Skip to content

Commit 5d846cf

Browse files
hyperpolymathclaude
andcommitted
feat: Gossamer migration — RuntimeBridge, gossamer.conf.json, Tauri→Gossamer conversion
Added Gossamer configuration and RuntimeBridge alongside existing Tauri setup. Tauri files preserved for transition period. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 016fa31 commit 5d846cf

3 files changed

Lines changed: 243 additions & 0 deletions

File tree

deno.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"build": "cd ui && deno task build",
88
"build:rust": "cd core && cargo build --release",
99
"build:tauri": "cd desktop && cargo tauri build",
10+
"gossamer:dev": "cd desktop && gossamer dev",
11+
"gossamer:build": "cd desktop && gossamer build",
1012
"test": "cd core && cargo test",
1113
"lint": "deno lint && cd core && cargo clippy",
1214
"fmt": "deno fmt && cd core && cargo fmt",

desktop/gossamer-bridge.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)
3+
4+
/**
5+
* gossamer-bridge.js — Unified IPC bridge for Nexia desktop app.
6+
*
7+
* Detects the available runtime (Gossamer, Tauri, or browser-only) and
8+
* dispatches `invoke` / `listen` calls to the appropriate backend. This
9+
* allows the Nexia desktop UI to work under both Gossamer and Tauri
10+
* during the migration period.
11+
*
12+
* Priority order:
13+
* 1. Gossamer (`window.__gossamer_invoke`) — own stack, preferred
14+
* 2. Tauri (`window.__TAURI__`) — legacy, transition
15+
* 3. Browser (stub / error) — development fallback
16+
*/
17+
18+
/**
19+
* Detect which desktop runtime is available.
20+
* @returns {"gossamer"|"tauri"|"browser"}
21+
*/
22+
function detectRuntime() {
23+
if (typeof window !== 'undefined' &&
24+
typeof window.__gossamer_invoke === 'function') {
25+
return 'gossamer';
26+
}
27+
if (typeof window !== 'undefined' &&
28+
window.__TAURI__ != null &&
29+
typeof window.__TAURI__.core !== 'undefined') {
30+
return 'tauri';
31+
}
32+
return 'browser';
33+
}
34+
35+
let _runtime = null;
36+
37+
/**
38+
* Get the cached runtime identifier.
39+
* @returns {"gossamer"|"tauri"|"browser"}
40+
*/
41+
export function runtime() {
42+
if (_runtime === null) {
43+
_runtime = detectRuntime();
44+
}
45+
return _runtime;
46+
}
47+
48+
/**
49+
* Invoke a backend command through whatever runtime is available.
50+
*
51+
* - On Gossamer: calls `window.__gossamer_invoke(cmd, args)`
52+
* - On Tauri: calls `window.__TAURI__.core.invoke(cmd, args)`
53+
* - On browser: rejects with a descriptive error
54+
*
55+
* @param {string} cmd — The command name
56+
* @param {object} [args] — Optional payload object
57+
* @returns {Promise<any>}
58+
*/
59+
export function invoke(cmd, args) {
60+
const rt = runtime();
61+
if (rt === 'gossamer') {
62+
return window.__gossamer_invoke(cmd, args || {});
63+
}
64+
if (rt === 'tauri') {
65+
return window.__TAURI__.core.invoke(cmd, args || {});
66+
}
67+
return Promise.reject(
68+
new Error(`No desktop runtime \u2014 "${cmd}" requires Gossamer or Tauri`)
69+
);
70+
}
71+
72+
/**
73+
* Listen for backend events.
74+
*
75+
* - On Gossamer: calls `window.__gossamer_listen(event, handler)`
76+
* - On Tauri: calls `window.__TAURI__.event.listen(event, handler)`
77+
* - On browser: returns a no-op unlisten function
78+
*
79+
* @param {string} event — The event name
80+
* @param {function} handler — Callback receiving `{ payload: ... }`
81+
* @returns {Promise<function>} Unlisten function
82+
*/
83+
export function listen(event, handler) {
84+
const rt = runtime();
85+
if (rt === 'gossamer') {
86+
if (typeof window.__gossamer_listen === 'function') {
87+
return window.__gossamer_listen(event, handler);
88+
}
89+
console.warn('[gossamer-bridge] Gossamer event listener not available for:', event);
90+
return Promise.resolve(() => {});
91+
}
92+
if (rt === 'tauri') {
93+
return window.__TAURI__.event.listen(event, handler);
94+
}
95+
console.warn('[gossamer-bridge] No desktop runtime \u2014 ignoring event listener for:', event);
96+
return Promise.resolve(() => {});
97+
}
98+
99+
/**
100+
* Whether any desktop runtime is available.
101+
* @returns {boolean}
102+
*/
103+
export function hasDesktopRuntime() {
104+
return runtime() !== 'browser';
105+
}
106+
107+
/**
108+
* Human-readable name for the current runtime.
109+
* @returns {string}
110+
*/
111+
export function runtimeName() {
112+
const names = { gossamer: 'Gossamer', tauri: 'Tauri', browser: 'Browser' };
113+
return names[runtime()] || 'Unknown';
114+
}

desktop/gossamer.conf.json

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
{
2+
"$schema": "https://gossamer.dev/schemas/config/v1",
3+
"productName": "Nexia",
4+
"version": "0.1.0",
5+
"identifier": "com.hyperpolymath.nexia",
6+
"build": {
7+
"frontendDist": "../web/dist",
8+
"devUrl": "http://localhost:5173",
9+
"beforeDevCommand": "cd ../ui && deno task dev",
10+
"beforeBuildCommand": "cd ../ui && deno task build"
11+
},
12+
"app": {
13+
"windows": [
14+
{
15+
"label": "main",
16+
"title": "Nexia",
17+
"width": 1200,
18+
"height": 800,
19+
"minWidth": 800,
20+
"minHeight": 600,
21+
"maxWidth": null,
22+
"maxHeight": null,
23+
"resizable": true,
24+
"fullscreen": false,
25+
"decorations": true,
26+
"transparent": false,
27+
"center": true,
28+
"alwaysOnTop": false,
29+
"visible": true,
30+
"url": "/"
31+
}
32+
],
33+
"security": {
34+
"csp": null,
35+
"capabilities": [
36+
"filesystem",
37+
"network",
38+
"shell",
39+
"clipboard",
40+
"notification"
41+
],
42+
"capabilityTokens": {
43+
"enabled": false,
44+
"issuer": "gossamer-runtime",
45+
"ttl": 3600
46+
},
47+
"sandbox": {
48+
"enabled": true,
49+
"allowExec": false,
50+
"allowNetwork": true,
51+
"allowFilesystem": true
52+
}
53+
},
54+
"ipc": {
55+
"protocol": "json",
56+
"bridgeInjection": true,
57+
"maxMessageSize": 16777216,
58+
"timeout": 30000
59+
},
60+
"tray": {
61+
"enabled": false,
62+
"icon": null,
63+
"tooltip": null,
64+
"menuOnLeftClick": true
65+
}
66+
},
67+
"plugins": {},
68+
"bundle": {
69+
"active": true,
70+
"targets": [
71+
"deb",
72+
"appimage",
73+
"dmg",
74+
"nsis"
75+
],
76+
"icon": [
77+
"icons/32x32.png",
78+
"icons/128x128.png",
79+
"icons/128x128@2x.png",
80+
"icons/icon.icns",
81+
"icons/icon.ico"
82+
],
83+
"resources": [],
84+
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)",
85+
"license": "PMPL-1.0-or-later",
86+
"shortDescription": "Nexia desktop application",
87+
"longDescription": "",
88+
"linux": {
89+
"desktopEntry": true,
90+
"category": "Utility",
91+
"section": "utils",
92+
"depends": [],
93+
"appimage": {
94+
"bundleMediaFramework": false
95+
}
96+
},
97+
"macos": {
98+
"bundleIdentifier": "com.hyperpolymath.nexia",
99+
"minimumSystemVersion": "11.0",
100+
"frameworks": [],
101+
"entitlements": null,
102+
"signingIdentity": null,
103+
"notarization": {
104+
"enabled": false,
105+
"teamId": null
106+
}
107+
},
108+
"windows": {
109+
"webview2": "embed",
110+
"certificateThumbprint": null,
111+
"wix": {
112+
"language": "en-US"
113+
},
114+
"nsis": {
115+
"displayLanguageSelector": false,
116+
"installerIcon": null
117+
}
118+
}
119+
},
120+
"ephapax": {
121+
"modules": [],
122+
"region": "app",
123+
"linearVerification": true,
124+
"regionBoundary": "strict",
125+
"preload": []
126+
}
127+
}

0 commit comments

Comments
 (0)