@rool-dev/app 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +306 -0
  2. package/dist/cli/dev.d.ts +10 -0
  3. package/dist/cli/dev.d.ts.map +1 -0
  4. package/dist/cli/dev.js +241 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +22 -0
  8. package/dist/cli/init.d.ts +7 -0
  9. package/dist/cli/init.d.ts.map +1 -0
  10. package/dist/cli/init.js +108 -0
  11. package/dist/cli/publish.d.ts +9 -0
  12. package/dist/cli/publish.d.ts.map +1 -0
  13. package/dist/cli/publish.js +213 -0
  14. package/dist/cli/vite-utils.d.ts +22 -0
  15. package/dist/cli/vite-utils.d.ts.map +1 -0
  16. package/dist/cli/vite-utils.js +96 -0
  17. package/dist/client.d.ts +79 -0
  18. package/dist/client.d.ts.map +1 -0
  19. package/dist/client.js +235 -0
  20. package/dist/dev/AppGrid.svelte +246 -0
  21. package/dist/dev/AppGrid.svelte.d.ts +14 -0
  22. package/dist/dev/AppGrid.svelte.d.ts.map +1 -0
  23. package/dist/dev/DevHostController.d.ts +86 -0
  24. package/dist/dev/DevHostController.d.ts.map +1 -0
  25. package/dist/dev/DevHostController.js +395 -0
  26. package/dist/dev/HostShell.svelte +110 -0
  27. package/dist/dev/HostShell.svelte.d.ts +11 -0
  28. package/dist/dev/HostShell.svelte.d.ts.map +1 -0
  29. package/dist/dev/Sidebar.svelte +223 -0
  30. package/dist/dev/Sidebar.svelte.d.ts +19 -0
  31. package/dist/dev/Sidebar.svelte.d.ts.map +1 -0
  32. package/dist/dev/TabBar.svelte +83 -0
  33. package/dist/dev/TabBar.svelte.d.ts +14 -0
  34. package/dist/dev/TabBar.svelte.d.ts.map +1 -0
  35. package/dist/dev/app.css +1 -0
  36. package/dist/dev/host-shell.d.ts +8 -0
  37. package/dist/dev/host-shell.d.ts.map +1 -0
  38. package/dist/dev/host-shell.js +14807 -0
  39. package/dist/dev/host-shell.js.map +1 -0
  40. package/dist/dev/vite-env.d.ts +4 -0
  41. package/dist/host.d.ts +54 -0
  42. package/dist/host.d.ts.map +1 -0
  43. package/dist/host.js +171 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +8 -0
  47. package/dist/manifest.d.ts +35 -0
  48. package/dist/manifest.d.ts.map +1 -0
  49. package/dist/manifest.js +10 -0
  50. package/dist/protocol.d.ts +46 -0
  51. package/dist/protocol.d.ts.map +1 -0
  52. package/dist/protocol.js +14 -0
  53. package/dist/reactive.svelte.d.ts +100 -0
  54. package/dist/reactive.svelte.d.ts.map +1 -0
  55. package/dist/reactive.svelte.js +267 -0
  56. package/dist/types.d.ts +119 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +7 -0
  59. package/package.json +78 -0
package/dist/client.js ADDED
@@ -0,0 +1,235 @@
1
+ /**
2
+ * App-side bridge client.
3
+ *
4
+ * `initApp()` waits for the host handshake, then returns an `AppChannel`
5
+ * that mirrors the RoolChannel API over postMessage.
6
+ */
7
+ import { isBridgeMessage } from './protocol.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ let _nextId = 0;
12
+ function nextRequestId() {
13
+ return `req-${++_nextId}-${Date.now().toString(36)}`;
14
+ }
15
+ // ---------------------------------------------------------------------------
16
+ // AppChannel
17
+ // ---------------------------------------------------------------------------
18
+ export class AppChannel {
19
+ _pending = new Map();
20
+ _listeners = new Map();
21
+ // Metadata from handshake
22
+ channelId;
23
+ spaceId;
24
+ spaceName;
25
+ role;
26
+ linkAccess;
27
+ userId;
28
+ _schema;
29
+ _metadata;
30
+ constructor(init) {
31
+ this.channelId = init.channelId;
32
+ this.spaceId = init.spaceId;
33
+ this.spaceName = init.spaceName;
34
+ this.role = init.role;
35
+ this.linkAccess = init.linkAccess;
36
+ this.userId = init.userId;
37
+ this._schema = init.schema;
38
+ this._metadata = init.metadata;
39
+ window.addEventListener('message', this._onMessage);
40
+ }
41
+ get isReadOnly() {
42
+ return this.role === 'viewer';
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Event emitter
46
+ // ---------------------------------------------------------------------------
47
+ on(event, callback) {
48
+ let set = this._listeners.get(event);
49
+ if (!set) {
50
+ set = new Set();
51
+ this._listeners.set(event, set);
52
+ }
53
+ set.add(callback);
54
+ }
55
+ off(event, callback) {
56
+ this._listeners.get(event)?.delete(callback);
57
+ }
58
+ _emit(event, data) {
59
+ const set = this._listeners.get(event);
60
+ if (set) {
61
+ for (const cb of set) {
62
+ try {
63
+ cb(data);
64
+ }
65
+ catch (e) {
66
+ console.error(`[AppChannel] Error in ${event} listener:`, e);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // postMessage transport
73
+ // ---------------------------------------------------------------------------
74
+ _call(method, ...args) {
75
+ return new Promise((resolve, reject) => {
76
+ const id = nextRequestId();
77
+ this._pending.set(id, { resolve, reject });
78
+ window.parent.postMessage({ type: 'rool:request', id, method, args }, '*');
79
+ });
80
+ }
81
+ _onMessage = (event) => {
82
+ if (!isBridgeMessage(event.data))
83
+ return;
84
+ if (event.data.type === 'rool:response') {
85
+ const msg = event.data;
86
+ const pending = this._pending.get(msg.id);
87
+ if (pending) {
88
+ this._pending.delete(msg.id);
89
+ if (msg.error) {
90
+ pending.reject(new Error(msg.error));
91
+ }
92
+ else {
93
+ pending.resolve(msg.result);
94
+ }
95
+ }
96
+ return;
97
+ }
98
+ if (event.data.type === 'rool:event') {
99
+ const msg = event.data;
100
+ this._emit(msg.name, msg.data);
101
+ // Keep local caches up to date
102
+ if (msg.name === 'metadataUpdated') {
103
+ const payload = msg.data;
104
+ this._metadata = payload.metadata;
105
+ }
106
+ return;
107
+ }
108
+ };
109
+ // ---------------------------------------------------------------------------
110
+ // Channel API — mirrors RoolChannel
111
+ // ---------------------------------------------------------------------------
112
+ // Object operations
113
+ async getObject(objectId) {
114
+ return this._call('getObject', objectId);
115
+ }
116
+ async stat(objectId) {
117
+ return this._call('stat', objectId);
118
+ }
119
+ async findObjects(options) {
120
+ return this._call('findObjects', options);
121
+ }
122
+ async getObjectIds(options) {
123
+ return this._call('getObjectIds', options);
124
+ }
125
+ async createObject(options) {
126
+ return this._call('createObject', options);
127
+ }
128
+ async updateObject(objectId, options) {
129
+ return this._call('updateObject', objectId, options);
130
+ }
131
+ async deleteObjects(objectIds) {
132
+ await this._call('deleteObjects', objectIds);
133
+ }
134
+ // Schema
135
+ getSchema() {
136
+ return this._schema;
137
+ }
138
+ async createCollection(name, fields) {
139
+ const result = await this._call('createCollection', name, fields);
140
+ this._schema[name] = result;
141
+ return result;
142
+ }
143
+ async alterCollection(name, fields) {
144
+ const result = await this._call('alterCollection', name, fields);
145
+ this._schema[name] = result;
146
+ return result;
147
+ }
148
+ async dropCollection(name) {
149
+ await this._call('dropCollection', name);
150
+ delete this._schema[name];
151
+ }
152
+ // Interactions & system instruction
153
+ async getInteractions() {
154
+ return this._call('getInteractions');
155
+ }
156
+ async getSystemInstruction() {
157
+ return this._call('getSystemInstruction');
158
+ }
159
+ async setSystemInstruction(instruction) {
160
+ await this._call('setSystemInstruction', instruction);
161
+ }
162
+ // Metadata
163
+ async setMetadata(key, value) {
164
+ await this._call('setMetadata', key, value);
165
+ this._metadata[key] = value;
166
+ }
167
+ getMetadata(key) {
168
+ return this._metadata[key];
169
+ }
170
+ getAllMetadata() {
171
+ return { ...this._metadata };
172
+ }
173
+ // AI
174
+ async prompt(text, options) {
175
+ return this._call('prompt', text, options);
176
+ }
177
+ // Undo/redo
178
+ async checkpoint(label) {
179
+ return this._call('checkpoint', label);
180
+ }
181
+ async canUndo() {
182
+ return this._call('canUndo');
183
+ }
184
+ async canRedo() {
185
+ return this._call('canRedo');
186
+ }
187
+ async undo() {
188
+ return this._call('undo');
189
+ }
190
+ async redo() {
191
+ return this._call('redo');
192
+ }
193
+ async clearHistory() {
194
+ await this._call('clearHistory');
195
+ }
196
+ // Cleanup
197
+ destroy() {
198
+ window.removeEventListener('message', this._onMessage);
199
+ for (const { reject } of this._pending.values()) {
200
+ reject(new Error('AppChannel destroyed'));
201
+ }
202
+ this._pending.clear();
203
+ this._listeners.clear();
204
+ }
205
+ }
206
+ // ---------------------------------------------------------------------------
207
+ // initApp
208
+ // ---------------------------------------------------------------------------
209
+ /**
210
+ * Initialize the app bridge. Call this once at startup.
211
+ *
212
+ * Sends `rool:ready` to the host and waits for `rool:init` with channel metadata.
213
+ * Returns an `AppChannel` that mirrors the RoolChannel API over postMessage.
214
+ *
215
+ * @param timeout - How long to wait for the handshake (ms). Default: 10000.
216
+ */
217
+ export function initApp(timeout = 10000) {
218
+ return new Promise((resolve, reject) => {
219
+ const timer = setTimeout(() => {
220
+ window.removeEventListener('message', onMessage);
221
+ reject(new Error('App handshake timed out — is this running inside a Rool host?'));
222
+ }, timeout);
223
+ function onMessage(event) {
224
+ if (!isBridgeMessage(event.data) || event.data.type !== 'rool:init')
225
+ return;
226
+ clearTimeout(timer);
227
+ window.removeEventListener('message', onMessage);
228
+ const channel = new AppChannel(event.data);
229
+ resolve(channel);
230
+ }
231
+ window.addEventListener('message', onMessage);
232
+ // Signal to the host that we're ready
233
+ window.parent.postMessage({ type: 'rool:ready' }, '*');
234
+ });
235
+ }
@@ -0,0 +1,246 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { GridStack } from 'gridstack';
4
+ import type { AppTab } from './DevHostController.js';
5
+ import type { DevHostController } from './DevHostController.js';
6
+ import type { PublishedAppInfo } from '@rool-dev/sdk';
7
+
8
+ interface Props {
9
+ controller: DevHostController;
10
+ tabs: AppTab[];
11
+ uninstalledApps: PublishedAppInfo[];
12
+ onInstallApp: (appId: string) => void;
13
+ onRemoveApp: (appId: string) => void;
14
+ }
15
+
16
+ let { controller, tabs, uninstalledApps, onInstallApp, onRemoveApp }: Props = $props();
17
+
18
+ let gridEl: HTMLDivElement;
19
+ let grid: GridStack;
20
+ let addMenuOpen = $state(false);
21
+
22
+ let mountedTabIds = new Set<string>();
23
+
24
+ // --- Layout persistence ---
25
+
26
+ interface SavedWidget { id: string; x: number; y: number; w: number; h: number }
27
+
28
+ function layoutKey(): string {
29
+ return `rool-devhost:layout:${controller.currentSpaceId ?? 'default'}`;
30
+ }
31
+
32
+ function loadLayout(): Record<string, SavedWidget> {
33
+ try {
34
+ const raw = localStorage.getItem(layoutKey());
35
+ if (!raw) return {};
36
+ const arr: SavedWidget[] = JSON.parse(raw);
37
+ const map: Record<string, SavedWidget> = {};
38
+ for (const w of arr) map[w.id] = w;
39
+ return map;
40
+ } catch { return {}; }
41
+ }
42
+
43
+ function saveLayout() {
44
+ if (!grid) return;
45
+ const widgets: SavedWidget[] = [];
46
+ for (const node of grid.engine.nodes) {
47
+ if (node.id) {
48
+ widgets.push({ id: node.id as string, x: node.x!, y: node.y!, w: node.w!, h: node.h! });
49
+ }
50
+ }
51
+ try { localStorage.setItem(layoutKey(), JSON.stringify(widgets)); } catch {}
52
+ }
53
+
54
+ function defaultPosition(count: number): { x: number; y: number; w: number; h: number } {
55
+ if (count <= 1) return { x: 0, y: 0, w: 12, h: 6 };
56
+ return {
57
+ w: 6,
58
+ h: 6,
59
+ x: ((count - 1) % 2) * 6,
60
+ y: Math.floor((count - 1) / 2) * 6,
61
+ };
62
+ }
63
+
64
+ onMount(() => {
65
+ grid = GridStack.init({
66
+ column: 12,
67
+ cellHeight: 80,
68
+ margin: 6,
69
+ animate: true,
70
+ float: true,
71
+ draggable: { handle: '.app-card-handle' },
72
+ resizable: { handles: 'e,se,s,sw,w' },
73
+ }, gridEl);
74
+
75
+ grid.on('dragstart resizestart', () => {
76
+ gridEl.classList.add('gs-dragging');
77
+ });
78
+ grid.on('dragstop resizestop', () => {
79
+ gridEl.classList.remove('gs-dragging');
80
+ });
81
+
82
+ // Save layout after any drag/resize/add/remove settles
83
+ grid.on('change', () => saveLayout());
84
+
85
+ const saved = loadLayout();
86
+ for (const tab of tabs) {
87
+ addTabWidget(tab, saved);
88
+ }
89
+
90
+ return () => {
91
+ grid.destroy(false);
92
+ };
93
+ });
94
+
95
+ function addTabWidget(tab: AppTab, savedLayout?: Record<string, SavedWidget>) {
96
+ if (mountedTabIds.has(tab.id)) return;
97
+ mountedTabIds.add(tab.id);
98
+
99
+ const saved = savedLayout?.[tab.id];
100
+ let { x, y, w, h } = saved ?? defaultPosition(mountedTabIds.size);
101
+
102
+ // If no saved layout and this is the second widget, shrink the first
103
+ if (!saved && mountedTabIds.size === 2) {
104
+ const firstEl = gridEl.querySelector(`[gs-id="${[...mountedTabIds][0]}"]`) as HTMLElement;
105
+ if (firstEl) grid.update(firstEl, { w: 6 });
106
+ }
107
+
108
+ const widgetEl = grid.addWidget({ id: tab.id, x, y, w, h, content: '' });
109
+
110
+ const contentEl = widgetEl.querySelector('.grid-stack-item-content') as HTMLElement;
111
+ contentEl.innerHTML = '';
112
+ contentEl.className = 'grid-stack-item-content flex flex-col overflow-hidden rounded-lg border bg-white shadow-sm'
113
+ + (tab.isLocal ? ' border-emerald-300' : ' border-slate-200');
114
+
115
+ // Title bar
116
+ const titleBar = document.createElement('div');
117
+ titleBar.className = 'app-card-handle flex items-center gap-1.5 px-2.5 h-8 border-b cursor-grab select-none shrink-0'
118
+ + (tab.isLocal
119
+ ? ' bg-emerald-50 border-emerald-200'
120
+ : ' bg-slate-50 border-slate-200');
121
+
122
+ if (tab.isLocal) {
123
+ const badge = document.createElement('span');
124
+ badge.className = 'text-[9px] font-bold text-emerald-700 bg-emerald-100 border border-emerald-300 rounded px-1 py-px tracking-wide';
125
+ badge.textContent = 'DEV';
126
+ titleBar.appendChild(badge);
127
+ }
128
+
129
+ const name = document.createElement('span');
130
+ name.className = 'text-xs font-medium text-slate-700 flex-1 min-w-0 truncate';
131
+ name.textContent = tab.name;
132
+ titleBar.appendChild(name);
133
+
134
+ if (!tab.isLocal) {
135
+ const id = document.createElement('span');
136
+ id.className = 'text-[10px] text-slate-400 font-mono shrink-0';
137
+ id.textContent = tab.id;
138
+ titleBar.appendChild(id);
139
+
140
+ const close = document.createElement('button');
141
+ close.className = 'border-none bg-transparent cursor-pointer text-slate-400 hover:text-red-500 p-0.5 leading-none shrink-0 transition-colors';
142
+ close.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>';
143
+ close.title = 'Uninstall app';
144
+ close.addEventListener('click', (e) => {
145
+ e.stopPropagation();
146
+ removeTabWidget(tab.id);
147
+ onRemoveApp(tab.id);
148
+ });
149
+ titleBar.appendChild(close);
150
+ }
151
+
152
+ contentEl.appendChild(titleBar);
153
+
154
+ const iframe = document.createElement('iframe');
155
+ iframe.src = tab.url;
156
+ iframe.title = tab.name;
157
+ iframe.sandbox.add('allow-scripts', 'allow-same-origin');
158
+ iframe.className = 'flex-1 border-0 min-h-0 w-full';
159
+ contentEl.appendChild(iframe);
160
+
161
+ controller.registerIframe(tab.id, iframe);
162
+ }
163
+
164
+ function removeTabWidget(tabId: string) {
165
+ const el = gridEl.querySelector(`[gs-id="${tabId}"]`) as HTMLElement;
166
+ if (el) {
167
+ controller.unregisterIframe(tabId);
168
+ grid.removeWidget(el);
169
+ mountedTabIds.delete(tabId);
170
+ saveLayout();
171
+ }
172
+ }
173
+
174
+ $effect(() => {
175
+ if (!grid) return;
176
+ const currentIds = new Set(tabs.map(t => t.id));
177
+
178
+ for (const tab of tabs) {
179
+ if (!mountedTabIds.has(tab.id)) addTabWidget(tab);
180
+ }
181
+ for (const id of mountedTabIds) {
182
+ if (!currentIds.has(id)) removeTabWidget(id);
183
+ }
184
+ });
185
+ </script>
186
+
187
+ <div class="flex-1 min-h-0 relative flex flex-col">
188
+ <!-- Toolbar -->
189
+ <div class="flex items-center px-3 py-1.5 bg-white border-b border-slate-200 shrink-0">
190
+ <span class="text-[11px] text-slate-400 font-semibold uppercase tracking-wide">Apps</span>
191
+ <div class="flex-1"></div>
192
+ {#if uninstalledApps.length > 0}
193
+ <div class="relative" data-add-menu>
194
+ <button
195
+ class="px-2.5 py-1 text-[11px] font-medium text-slate-500 hover:text-slate-700 hover:bg-slate-50 rounded-md transition-colors border border-slate-200 bg-white flex items-center gap-1.5"
196
+ type="button"
197
+ onclick={(e: MouseEvent) => { e.stopPropagation(); addMenuOpen = !addMenuOpen; }}
198
+ >
199
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
200
+ Install
201
+ </button>
202
+ {#if addMenuOpen}
203
+ <div class="absolute top-full mt-1 right-0 min-w-[200px] max-h-60 overflow-y-auto bg-white border border-slate-200 rounded-lg shadow-lg z-50 py-1">
204
+ {#each uninstalledApps as app}
205
+ <button
206
+ class="flex items-center gap-2 w-full px-2.5 py-1.5 text-[13px] text-left border-none cursor-pointer hover:bg-slate-50 text-slate-700 bg-transparent"
207
+ type="button"
208
+ onclick={() => { addMenuOpen = false; onInstallApp(app.appId); }}
209
+ >
210
+ <span class="font-medium">{app.name}</span>
211
+ <span class="text-[10px] text-slate-400 font-mono ml-auto">{app.appId}</span>
212
+ </button>
213
+ {/each}
214
+ </div>
215
+ {/if}
216
+ </div>
217
+ {/if}
218
+ </div>
219
+
220
+ <!-- Grid -->
221
+ <div class="flex-1 min-h-0 overflow-auto bg-slate-100 p-2">
222
+ <div bind:this={gridEl} class="grid-stack"></div>
223
+ </div>
224
+ </div>
225
+
226
+ <svelte:document onclick={(e: MouseEvent) => {
227
+ if (addMenuOpen && !(e.target as Element)?.closest('[data-add-menu]')) {
228
+ addMenuOpen = false;
229
+ }
230
+ }} />
231
+
232
+ <style>
233
+ /* Only GridStack overrides that can't be done with Tailwind */
234
+ :global(.gs-dragging .grid-stack-item-content iframe) {
235
+ pointer-events: none !important;
236
+ }
237
+ :global(.ui-draggable-dragging > .grid-stack-item-content),
238
+ :global(.ui-resizable-resizing > .grid-stack-item-content) {
239
+ opacity: 1 !important;
240
+ }
241
+ :global(.grid-stack-placeholder > .placeholder-content) {
242
+ background: rgba(99, 102, 241, 0.06) !important;
243
+ border: 2px dashed #c7d2fe !important;
244
+ border-radius: 8px !important;
245
+ }
246
+ </style>
@@ -0,0 +1,14 @@
1
+ import type { AppTab } from './DevHostController.js';
2
+ import type { DevHostController } from './DevHostController.js';
3
+ import type { PublishedAppInfo } from '@rool-dev/sdk';
4
+ interface Props {
5
+ controller: DevHostController;
6
+ tabs: AppTab[];
7
+ uninstalledApps: PublishedAppInfo[];
8
+ onInstallApp: (appId: string) => void;
9
+ onRemoveApp: (appId: string) => void;
10
+ }
11
+ declare const AppGrid: import("svelte").Component<Props, {}, "">;
12
+ type AppGrid = ReturnType<typeof AppGrid>;
13
+ export default AppGrid;
14
+ //# sourceMappingURL=AppGrid.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppGrid.svelte.d.ts","sourceRoot":"","sources":["../../src/dev/AppGrid.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGpD,UAAU,KAAK;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,eAAe,EAAE,gBAAgB,EAAE,CAAC;IACpC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA8NH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * DevHostController — business logic for the dev host shell.
3
+ *
4
+ * Owns the RoolClient lifecycle, space management, channel-per-app management,
5
+ * bridge hosting, and published app management. The Svelte component is a thin
6
+ * view layer that reads this controller's state and calls its methods.
7
+ *
8
+ * The controller is self-sufficient: it manages the full lifecycle including
9
+ * DOM flush (via injected tick) and bridge binding. Svelte components can call
10
+ * controller methods directly without needing wrappers.
11
+ */
12
+ import { RoolClient } from '@rool-dev/sdk';
13
+ import type { RoolSpaceInfo, PublishedAppInfo } from '@rool-dev/sdk';
14
+ import type { AppManifest, Environment } from '../manifest.js';
15
+ export interface AppTab {
16
+ id: string;
17
+ name: string;
18
+ url: string;
19
+ isLocal: boolean;
20
+ }
21
+ export type StatusState = 'ok' | 'loading' | 'off';
22
+ export declare class DevHostController {
23
+ readonly channelId: string;
24
+ readonly appUrl: string;
25
+ readonly manifest: AppManifest | null;
26
+ readonly manifestError: string | null;
27
+ client: RoolClient;
28
+ spaces: RoolSpaceInfo[];
29
+ currentSpaceId: string | null;
30
+ statusText: string;
31
+ statusState: StatusState;
32
+ placeholderText: string | null;
33
+ env: Environment;
34
+ publishedApps: PublishedAppInfo[];
35
+ installedAppIds: string[];
36
+ sidebarCollapsed: boolean;
37
+ private channels;
38
+ private iframeEls;
39
+ private bridgeHosts;
40
+ private _onChange;
41
+ private _tick;
42
+ private _spaceKey;
43
+ constructor(options: {
44
+ channelId: string;
45
+ appUrl: string;
46
+ manifest: AppManifest | null;
47
+ manifestError: string | null;
48
+ }, onChange: () => void, tick: () => Promise<void>);
49
+ get tabs(): AppTab[];
50
+ boot(): Promise<void>;
51
+ selectSpace(spaceId: string): Promise<void>;
52
+ /**
53
+ * Install an app in the current space.
54
+ *
55
+ * Opens the channel first, THEN adds the tab. This ensures the channel
56
+ * exists when the iframe mounts so registerIframe → _bindBridge can
57
+ * connect the bridge before the app sends its init message.
58
+ */
59
+ installApp(appId: string): Promise<void>;
60
+ /**
61
+ * Uninstall an app from the current space.
62
+ * Deletes the channel and removes the card.
63
+ */
64
+ removeApp(appId: string): void;
65
+ switchEnv(newEnv: Environment): Promise<void>;
66
+ toggleSidebar(): void;
67
+ registerIframe(tabId: string, el: HTMLIFrameElement): void;
68
+ unregisterIframe(tabId: string): void;
69
+ logout(): void;
70
+ private _bindBridge;
71
+ private _bindAllBridges;
72
+ private _destroyTab;
73
+ private _destroyAllBridgesAndChannels;
74
+ /**
75
+ * Fetch `rool-app.json` from a published app's URL.
76
+ * Returns null if the fetch fails (app might not have a manifest).
77
+ */
78
+ private _fetchRemoteManifest;
79
+ /**
80
+ * Idempotently sync a manifest's settings (name, system instruction, collections)
81
+ * onto a channel. Safe to call every time the app is opened.
82
+ */
83
+ private _syncManifest;
84
+ private _getSavedEnv;
85
+ }
86
+ //# sourceMappingURL=DevHostController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevHostController.d.ts","sourceRoot":"","sources":["../../src/dev/DevHostController.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAe,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElF,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAO/D,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC;AAqBnD,qBAAa,iBAAiB;IAE5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAGtC,MAAM,EAAG,UAAU,CAAC;IAGpB,MAAM,EAAE,aAAa,EAAE,CAAM;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IACrC,UAAU,EAAE,MAAM,CAAqB;IACvC,WAAW,EAAE,WAAW,CAAS;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAuB;IACrD,GAAG,EAAE,WAAW,CAAC;IACjB,aAAa,EAAE,gBAAgB,EAAE,CAAM;IACvC,eAAe,EAAE,MAAM,EAAE,CAAM;IAC/B,gBAAgB,EAAE,OAAO,CAAS;IAGlC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,WAAW,CAAkC;IAGrD,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,KAAK,CAAsB;IAGnC,OAAO,CAAC,SAAS,CAAS;gBAGxB,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;QAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,EACD,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;IAoB3B,IAAI,IAAI,IAAI,MAAM,EAAE,CAoBnB;IAMK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+DrB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgEjD;;;;;;OAMG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B9C;;;OAGG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAiBxB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnD,aAAa,IAAI,IAAI;IAUrB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI;IAK1D,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQrC,MAAM,IAAI,IAAI;IASd,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,6BAA6B;IAYrC;;;OAGG;YACW,oBAAoB;IAYlC;;;OAGG;YACW,aAAa;IA8B3B,OAAO,CAAC,YAAY;CAKrB"}