@rool-dev/extension 0.3.5

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 (65) hide show
  1. package/README.md +458 -0
  2. package/dist/cli/build-pipeline.d.ts +18 -0
  3. package/dist/cli/build-pipeline.d.ts.map +1 -0
  4. package/dist/cli/build-pipeline.js +160 -0
  5. package/dist/cli/build.d.ts +9 -0
  6. package/dist/cli/build.d.ts.map +1 -0
  7. package/dist/cli/build.js +17 -0
  8. package/dist/cli/dev.d.ts +10 -0
  9. package/dist/cli/dev.d.ts.map +1 -0
  10. package/dist/cli/dev.js +257 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +34 -0
  14. package/dist/cli/init.d.ts +8 -0
  15. package/dist/cli/init.d.ts.map +1 -0
  16. package/dist/cli/init.js +113 -0
  17. package/dist/cli/publish.d.ts +9 -0
  18. package/dist/cli/publish.d.ts.map +1 -0
  19. package/dist/cli/publish.js +65 -0
  20. package/dist/cli/vite-utils.d.ts +23 -0
  21. package/dist/cli/vite-utils.d.ts.map +1 -0
  22. package/dist/cli/vite-utils.js +105 -0
  23. package/dist/client.d.ts +139 -0
  24. package/dist/client.d.ts.map +1 -0
  25. package/dist/client.js +360 -0
  26. package/dist/dev/AppGrid.svelte +246 -0
  27. package/dist/dev/AppGrid.svelte.d.ts +14 -0
  28. package/dist/dev/AppGrid.svelte.d.ts.map +1 -0
  29. package/dist/dev/DevHostController.d.ts +85 -0
  30. package/dist/dev/DevHostController.d.ts.map +1 -0
  31. package/dist/dev/DevHostController.js +429 -0
  32. package/dist/dev/HostShell.svelte +119 -0
  33. package/dist/dev/HostShell.svelte.d.ts +11 -0
  34. package/dist/dev/HostShell.svelte.d.ts.map +1 -0
  35. package/dist/dev/Sidebar.svelte +290 -0
  36. package/dist/dev/Sidebar.svelte.d.ts +22 -0
  37. package/dist/dev/Sidebar.svelte.d.ts.map +1 -0
  38. package/dist/dev/TabBar.svelte +83 -0
  39. package/dist/dev/TabBar.svelte.d.ts +14 -0
  40. package/dist/dev/TabBar.svelte.d.ts.map +1 -0
  41. package/dist/dev/app.css +1 -0
  42. package/dist/dev/host-shell.d.ts +8 -0
  43. package/dist/dev/host-shell.d.ts.map +1 -0
  44. package/dist/dev/host-shell.js +15282 -0
  45. package/dist/dev/host-shell.js.map +1 -0
  46. package/dist/dev/vite-env.d.ts +4 -0
  47. package/dist/host.d.ts +55 -0
  48. package/dist/host.d.ts.map +1 -0
  49. package/dist/host.js +203 -0
  50. package/dist/index.d.ts +10 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +8 -0
  53. package/dist/manifest.d.ts +40 -0
  54. package/dist/manifest.d.ts.map +1 -0
  55. package/dist/manifest.js +11 -0
  56. package/dist/protocol.d.ts +48 -0
  57. package/dist/protocol.d.ts.map +1 -0
  58. package/dist/protocol.js +14 -0
  59. package/dist/reactive.svelte.d.ts +150 -0
  60. package/dist/reactive.svelte.d.ts.map +1 -0
  61. package/dist/reactive.svelte.js +362 -0
  62. package/dist/types.d.ts +139 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +7 -0
  65. package/package.json +79 -0
@@ -0,0 +1,429 @@
1
+ /**
2
+ * DevHostController — business logic for the dev host shell.
3
+ *
4
+ * Owns the RoolClient lifecycle, space management, channel-per-extension management,
5
+ * bridge hosting, and published extension 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 { createBridgeHost } from '../host.js';
14
+ import { ENV_URLS } from '../manifest.js';
15
+ // ---------------------------------------------------------------------------
16
+ // localStorage helpers
17
+ // ---------------------------------------------------------------------------
18
+ function storageGet(key) {
19
+ try {
20
+ return localStorage.getItem(key);
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function storageSet(key, value) {
27
+ try {
28
+ if (value === null)
29
+ localStorage.removeItem(key);
30
+ else
31
+ localStorage.setItem(key, value);
32
+ }
33
+ catch { /* ignore */ }
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // DevHostController
37
+ // ---------------------------------------------------------------------------
38
+ export class DevHostController {
39
+ // --- Config (immutable after construction) ---
40
+ channelId;
41
+ extensionUrl;
42
+ manifest;
43
+ manifestError;
44
+ // --- SDK client ---
45
+ client;
46
+ // --- Observable state (Svelte component mirrors these via $state) ---
47
+ spaces = [];
48
+ currentSpaceId = null;
49
+ statusText = 'Initializing...';
50
+ statusState = 'off';
51
+ placeholderText = 'Authenticating...';
52
+ env;
53
+ publishedExtensions = [];
54
+ installedExtensionIds = [];
55
+ sidebarCollapsed = false;
56
+ publishState = 'idle';
57
+ publishMessage = null;
58
+ publishUrl = null;
59
+ // --- Per-tab state (imperative, not rendered directly) ---
60
+ channels = {};
61
+ iframeEls = {};
62
+ bridgeHosts = {};
63
+ // --- Dependencies ---
64
+ _onChange;
65
+ _tick;
66
+ // --- Storage keys ---
67
+ _spaceKey;
68
+ constructor(options, onChange, tick) {
69
+ this.channelId = options.channelId;
70
+ this.extensionUrl = options.extensionUrl;
71
+ this.manifest = options.manifest;
72
+ this.manifestError = options.manifestError;
73
+ this._onChange = onChange;
74
+ this._tick = tick;
75
+ this._spaceKey = `rool-devhost:${options.channelId}:space`;
76
+ // Restore persisted state
77
+ this.env = this._getSavedEnv();
78
+ this.sidebarCollapsed = storageGet('rool-devhost:collapsed') === 'true';
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Derived
82
+ // ---------------------------------------------------------------------------
83
+ get tabs() {
84
+ const localTab = {
85
+ id: 'local',
86
+ name: this.manifest?.name ?? 'Local',
87
+ url: this.extensionUrl,
88
+ isLocal: true,
89
+ };
90
+ const extensionTabs = this.installedExtensionIds
91
+ .map((id) => {
92
+ const ch = this.channels[id];
93
+ if (!ch?.extensionUrl)
94
+ return null;
95
+ return {
96
+ id,
97
+ name: ch.channelName ?? id,
98
+ url: ch.extensionUrl,
99
+ isLocal: false,
100
+ };
101
+ })
102
+ .filter((t) => t !== null);
103
+ return [localTab, ...extensionTabs];
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Bootstrap
107
+ // ---------------------------------------------------------------------------
108
+ async boot() {
109
+ const urls = ENV_URLS[this.env];
110
+ this.client = new RoolClient({ baseUrl: urls.baseUrl, authUrl: urls.authUrl });
111
+ const authenticated = await this.client.initialize();
112
+ if (!authenticated) {
113
+ this.placeholderText = 'Redirecting to login...';
114
+ this.statusText = 'Authenticating...';
115
+ this.statusState = 'loading';
116
+ this._onChange();
117
+ this.client.login('Extension Dev Host');
118
+ return;
119
+ }
120
+ this.placeholderText = 'Loading spaces...';
121
+ this.statusText = 'Loading spaces...';
122
+ this.statusState = 'loading';
123
+ this._onChange();
124
+ const [spaceList, extensionList] = await Promise.all([
125
+ this.client.listSpaces(),
126
+ this.client.listExtensions().catch(() => []),
127
+ ]);
128
+ this.spaces = spaceList;
129
+ this.publishedExtensions = extensionList;
130
+ this.client.on('spaceAdded', (space) => {
131
+ if (!this.spaces.some((s) => s.id === space.id)) {
132
+ this.spaces = [...this.spaces, space];
133
+ this._onChange();
134
+ }
135
+ });
136
+ this.client.on('spaceRemoved', (id) => {
137
+ this.spaces = this.spaces.filter((s) => s.id !== id);
138
+ if (this.currentSpaceId === id) {
139
+ this.currentSpaceId = null;
140
+ this.statusText = 'Disconnected';
141
+ this.statusState = 'off';
142
+ }
143
+ this._onChange();
144
+ });
145
+ this.client.on('spaceRenamed', (id, name) => {
146
+ this.spaces = this.spaces.map((s) => (s.id === id ? { ...s, name } : s));
147
+ this._onChange();
148
+ });
149
+ this.statusText = 'Ready';
150
+ this.statusState = 'off';
151
+ const savedSpace = storageGet(this._spaceKey);
152
+ if (savedSpace && this.spaces.some((s) => s.id === savedSpace)) {
153
+ await this.selectSpace(savedSpace);
154
+ }
155
+ else {
156
+ this.placeholderText = 'Select a space to load the extension';
157
+ this._onChange();
158
+ }
159
+ }
160
+ // ---------------------------------------------------------------------------
161
+ // Space selection
162
+ // ---------------------------------------------------------------------------
163
+ async selectSpace(spaceId) {
164
+ this._destroyAllBridgesAndChannels();
165
+ this.currentSpaceId = spaceId;
166
+ storageSet(this._spaceKey, spaceId);
167
+ this.statusText = 'Opening channels...';
168
+ this.statusState = 'loading';
169
+ this.placeholderText = 'Opening channels...';
170
+ this._onChange();
171
+ try {
172
+ // Open the local extension's channel
173
+ const localChannel = await this.client.openChannel(spaceId, this.channelId);
174
+ this.channels['local'] = localChannel;
175
+ // Apply manifest settings to the local channel
176
+ await this._syncManifest(localChannel, this.manifest);
177
+ // Discover installed extensions: channels with an extensionUrl
178
+ const space = await this.client.openSpace(spaceId);
179
+ const spaceChannels = space.getChannels();
180
+ this.installedExtensionIds = spaceChannels
181
+ .filter((ch) => ch.extensionUrl && ch.id !== this.channelId)
182
+ .map((ch) => ch.id);
183
+ // Open channels for each installed extension (server already applied manifest)
184
+ for (const extId of this.installedExtensionIds) {
185
+ try {
186
+ const ch = await this.client.openChannel(spaceId, extId);
187
+ this.channels[extId] = ch;
188
+ }
189
+ catch (e) {
190
+ console.error(`Failed to open channel for extension ${extId}:`, e);
191
+ }
192
+ }
193
+ // Show iframes, wait for DOM to mount them, then bind bridges
194
+ this.placeholderText = null;
195
+ this._onChange();
196
+ await this._tick();
197
+ this._bindAllBridges();
198
+ const spaceName = this.spaces.find((s) => s.id === this.currentSpaceId)?.name ?? spaceId;
199
+ this.statusText = `Connected \u2014 ${spaceName}`;
200
+ this.statusState = 'ok';
201
+ this._onChange();
202
+ }
203
+ catch (e) {
204
+ console.error('Failed to open channel:', e);
205
+ this.placeholderText = `Error: ${e instanceof Error ? e.message : String(e)}`;
206
+ this.statusText = 'Error';
207
+ this.statusState = 'off';
208
+ this._onChange();
209
+ }
210
+ }
211
+ // ---------------------------------------------------------------------------
212
+ // Extension installation / removal
213
+ // ---------------------------------------------------------------------------
214
+ /**
215
+ * Install an extension in the current space.
216
+ *
217
+ * Opens the channel first, THEN adds the tab. This ensures the channel
218
+ * exists when the iframe mounts so registerIframe → _bindBridge can
219
+ * connect the bridge before the extension sends its init message.
220
+ */
221
+ async installExtension(extensionId) {
222
+ if (!this.currentSpaceId)
223
+ return;
224
+ if (this.installedExtensionIds.includes(extensionId))
225
+ return;
226
+ try {
227
+ // Step 1: install extension (server applies manifest: name, systemInstruction, collections)
228
+ const channelId = await this.client.installExtension(this.currentSpaceId, extensionId);
229
+ // Step 2: open channel for live subscription
230
+ const ch = await this.client.openChannel(this.currentSpaceId, channelId);
231
+ this.channels[extensionId] = ch;
232
+ // Step 3: add the card, flush DOM, bind bridge
233
+ this.installedExtensionIds = [...this.installedExtensionIds, extensionId];
234
+ this._onChange();
235
+ await this._tick();
236
+ this._bindBridge(extensionId);
237
+ }
238
+ catch (e) {
239
+ console.error(`Failed to install extension ${extensionId}:`, e);
240
+ this.installedExtensionIds = this.installedExtensionIds.filter((id) => id !== extensionId);
241
+ this._onChange();
242
+ }
243
+ }
244
+ /**
245
+ * Uninstall an extension from the current space.
246
+ * Deletes the channel and removes the card.
247
+ */
248
+ removeExtension(extensionId) {
249
+ this._destroyTab(extensionId);
250
+ this.installedExtensionIds = this.installedExtensionIds.filter((id) => id !== extensionId);
251
+ this._onChange();
252
+ // Delete the channel in the background (fire-and-forget)
253
+ if (this.currentSpaceId) {
254
+ this.client.deleteChannel(this.currentSpaceId, extensionId).catch((e) => {
255
+ console.error(`Failed to delete channel for extension ${extensionId}:`, e);
256
+ });
257
+ }
258
+ }
259
+ // ---------------------------------------------------------------------------
260
+ // Publishing
261
+ // ---------------------------------------------------------------------------
262
+ async publish() {
263
+ if (!this.manifest) {
264
+ this.publishState = 'error';
265
+ this.publishMessage = 'No valid manifest found';
266
+ this._onChange();
267
+ return;
268
+ }
269
+ this.publishState = 'building';
270
+ this.publishMessage = null;
271
+ this.publishUrl = null;
272
+ this._onChange();
273
+ try {
274
+ // Step 1: trigger server-side Vite build + zip
275
+ const buildRes = await fetch('/__rool-host/publish', { method: 'POST' });
276
+ if (!buildRes.ok) {
277
+ const body = await buildRes.json().catch(() => ({ error: 'Build failed' }));
278
+ throw new Error(body.error || 'Build failed');
279
+ }
280
+ const zipBlob = await buildRes.blob();
281
+ // Step 2: publish via SDK
282
+ this.publishState = 'uploading';
283
+ this._onChange();
284
+ const result = await this.client.publishExtension(this.manifest.id, {
285
+ bundle: zipBlob,
286
+ });
287
+ // Step 3: update published extensions list
288
+ const existingIdx = this.publishedExtensions.findIndex((a) => a.extensionId === result.extensionId);
289
+ if (existingIdx >= 0) {
290
+ this.publishedExtensions = [
291
+ ...this.publishedExtensions.slice(0, existingIdx),
292
+ result,
293
+ ...this.publishedExtensions.slice(existingIdx + 1),
294
+ ];
295
+ }
296
+ else {
297
+ this.publishedExtensions = [...this.publishedExtensions, result];
298
+ }
299
+ this.publishState = 'done';
300
+ this.publishUrl = result.url;
301
+ this._onChange();
302
+ // Auto-clear success state after 5 seconds
303
+ setTimeout(() => {
304
+ if (this.publishState === 'done') {
305
+ this.publishState = 'idle';
306
+ this.publishUrl = null;
307
+ this._onChange();
308
+ }
309
+ }, 5000);
310
+ }
311
+ catch (e) {
312
+ this.publishState = 'error';
313
+ this.publishMessage = e instanceof Error ? e.message : String(e);
314
+ this._onChange();
315
+ }
316
+ }
317
+ // ---------------------------------------------------------------------------
318
+ // Environment switching
319
+ // ---------------------------------------------------------------------------
320
+ async switchEnv(newEnv) {
321
+ if (newEnv === this.env)
322
+ return;
323
+ this.env = newEnv;
324
+ storageSet('rool-devhost:env', newEnv);
325
+ this._destroyAllBridgesAndChannels();
326
+ this.currentSpaceId = null;
327
+ this.spaces = [];
328
+ this.publishedExtensions = [];
329
+ this.installedExtensionIds = [];
330
+ this._onChange();
331
+ await this.boot();
332
+ }
333
+ // ---------------------------------------------------------------------------
334
+ // Sidebar
335
+ // ---------------------------------------------------------------------------
336
+ toggleSidebar() {
337
+ this.sidebarCollapsed = !this.sidebarCollapsed;
338
+ storageSet('rool-devhost:collapsed', String(this.sidebarCollapsed));
339
+ this._onChange();
340
+ }
341
+ // ---------------------------------------------------------------------------
342
+ // Iframe registration (called by Svelte action)
343
+ // ---------------------------------------------------------------------------
344
+ registerIframe(tabId, el) {
345
+ this.iframeEls[tabId] = el;
346
+ this._bindBridge(tabId);
347
+ }
348
+ unregisterIframe(tabId) {
349
+ delete this.iframeEls[tabId];
350
+ }
351
+ // ---------------------------------------------------------------------------
352
+ // Cleanup
353
+ // ---------------------------------------------------------------------------
354
+ logout() {
355
+ this.client.logout();
356
+ window.location.reload();
357
+ }
358
+ // ---------------------------------------------------------------------------
359
+ // Private helpers
360
+ // ---------------------------------------------------------------------------
361
+ _bindBridge(tabId) {
362
+ const el = this.iframeEls[tabId];
363
+ const ch = this.channels[tabId];
364
+ if (el && ch && !this.bridgeHosts[tabId]) {
365
+ this.bridgeHosts[tabId] = createBridgeHost({ channel: ch, iframe: el });
366
+ }
367
+ }
368
+ _bindAllBridges() {
369
+ for (const tab of this.tabs) {
370
+ this._bindBridge(tab.id);
371
+ }
372
+ }
373
+ _destroyTab(tabId) {
374
+ this.bridgeHosts[tabId]?.destroy();
375
+ delete this.bridgeHosts[tabId];
376
+ this.channels[tabId]?.close();
377
+ delete this.channels[tabId];
378
+ delete this.iframeEls[tabId];
379
+ }
380
+ _destroyAllBridgesAndChannels() {
381
+ for (const host of Object.values(this.bridgeHosts)) {
382
+ host.destroy();
383
+ }
384
+ for (const ch of Object.values(this.channels)) {
385
+ ch.close();
386
+ }
387
+ this.bridgeHosts = {};
388
+ this.channels = {};
389
+ this.iframeEls = {};
390
+ }
391
+ /**
392
+ * Idempotently sync a manifest's settings (name, system instruction, collections)
393
+ * onto a channel. Safe to call every time the extension is opened.
394
+ */
395
+ async _syncManifest(channel, manifest) {
396
+ if (!manifest)
397
+ return;
398
+ if (channel.channelName !== manifest.name) {
399
+ await channel.rename(manifest.name);
400
+ }
401
+ const targetInstruction = manifest.systemInstruction ?? null;
402
+ const currentInstruction = channel.getSystemInstruction() ?? null;
403
+ if (currentInstruction !== targetInstruction) {
404
+ await channel.setSystemInstruction(targetInstruction);
405
+ }
406
+ const currentSchema = channel.getSchema();
407
+ const syncCollections = async (colls) => {
408
+ for (const [name, fields] of Object.entries(colls)) {
409
+ if (name in currentSchema) {
410
+ await channel.alterCollection(name, fields);
411
+ }
412
+ else {
413
+ await channel.createCollection(name, fields);
414
+ }
415
+ }
416
+ };
417
+ const { write: w, read: r } = manifest.collections;
418
+ if (w && w !== '*')
419
+ await syncCollections(w);
420
+ if (r && r !== '*')
421
+ await syncCollections(r);
422
+ }
423
+ _getSavedEnv() {
424
+ const saved = storageGet('rool-devhost:env');
425
+ if (saved === 'local' || saved === 'dev' || saved === 'prod')
426
+ return saved;
427
+ return 'prod';
428
+ }
429
+ }
@@ -0,0 +1,119 @@
1
+ <script lang="ts">
2
+ import { onMount, tick } from 'svelte';
3
+ import { DevHostController } from './DevHostController.js';
4
+ import type { ExtensionTab } from './DevHostController.js';
5
+ import type { Manifest } from '../manifest.js';
6
+ import type { RoolSpaceInfo, PublishedExtensionInfo } from '@rool-dev/sdk';
7
+ import type { Environment } from '../manifest.js';
8
+ import Sidebar from './Sidebar.svelte';
9
+ import AppGrid from './AppGrid.svelte';
10
+
11
+ // Props injected from the mount entry
12
+ interface Props {
13
+ channelId: string;
14
+ extensionUrl: string;
15
+ manifest: Manifest | null;
16
+ manifestError: string | null;
17
+ }
18
+
19
+ const props: Props = $props();
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Controller + reactive state mirror
23
+ // ---------------------------------------------------------------------------
24
+
25
+ let spaces: RoolSpaceInfo[] = $state([]);
26
+ let currentSpaceId: string | null = $state(null);
27
+ let statusText: string = $state('Initializing...');
28
+ let statusState: 'ok' | 'loading' | 'off' = $state('off');
29
+ let placeholderText: string | null = $state('Authenticating...');
30
+ let sidebarCollapsed: boolean = $state(false);
31
+ let env: Environment = $state('prod');
32
+ let publishedExtensions: PublishedExtensionInfo[] = $state([]);
33
+ let installedExtensionIds: string[] = $state([]);
34
+ let tabs: ExtensionTab[] = $state([]);
35
+ let publishState: 'idle' | 'building' | 'uploading' | 'done' | 'error' = $state('idle');
36
+ let publishMessage: string | null = $state(null);
37
+ let publishUrl: string | null = $state(null);
38
+
39
+ // UI-only state (not in controller)
40
+ let dropdownOpen: boolean = $state(false);
41
+
42
+ const controller = new DevHostController(
43
+ props,
44
+ syncState,
45
+ tick,
46
+ );
47
+
48
+ function syncState() {
49
+ spaces = controller.spaces;
50
+ currentSpaceId = controller.currentSpaceId;
51
+ statusText = controller.statusText;
52
+ statusState = controller.statusState;
53
+ placeholderText = controller.placeholderText;
54
+ sidebarCollapsed = controller.sidebarCollapsed;
55
+ env = controller.env;
56
+ publishedExtensions = controller.publishedExtensions;
57
+ installedExtensionIds = controller.installedExtensionIds;
58
+ tabs = controller.tabs;
59
+ publishState = controller.publishState;
60
+ publishMessage = controller.publishMessage;
61
+ publishUrl = controller.publishUrl;
62
+ }
63
+
64
+ // Derived: published apps not yet installed (excluding the local dev app)
65
+ let uninstalledExtensions = $derived(
66
+ publishedExtensions.filter((ext) => ext.extensionId !== props.channelId && !installedExtensionIds.includes(ext.extensionId)),
67
+ );
68
+
69
+ // Initial sync
70
+ syncState();
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Bootstrap
74
+ // ---------------------------------------------------------------------------
75
+
76
+ onMount(() => {
77
+ controller.boot();
78
+
79
+ function handleClickOutside(e: MouseEvent) {
80
+ if (dropdownOpen && !(e.target as Element)?.closest('[data-dropdown]')) {
81
+ dropdownOpen = false;
82
+ }
83
+ }
84
+ document.addEventListener('click', handleClickOutside);
85
+ return () => document.removeEventListener('click', handleClickOutside);
86
+ });
87
+ </script>
88
+
89
+ <!-- Sidebar -->
90
+ <Sidebar
91
+ {controller}
92
+ manifest={props.manifest}
93
+ manifestError={props.manifestError}
94
+ {spaces}
95
+ {currentSpaceId}
96
+ {env}
97
+ {statusText}
98
+ {statusState}
99
+ {sidebarCollapsed}
100
+ {publishState}
101
+ {publishMessage}
102
+ {publishUrl}
103
+ bind:dropdownOpen
104
+ />
105
+
106
+ <!-- Main area -->
107
+ <div class="flex-1 min-w-0 flex flex-col">
108
+ {#if placeholderText}
109
+ <div class="flex items-center justify-center h-full text-slate-400 text-sm">{placeholderText}</div>
110
+ {:else}
111
+ <AppGrid
112
+ {controller}
113
+ {tabs}
114
+ uninstalledExtensions={uninstalledExtensions}
115
+ onInstallExtension={(id) => controller.installExtension(id)}
116
+ onRemoveExtension={(id) => controller.removeExtension(id)}
117
+ />
118
+ {/if}
119
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { Manifest } from '../manifest.js';
2
+ interface Props {
3
+ channelId: string;
4
+ extensionUrl: string;
5
+ manifest: Manifest | null;
6
+ manifestError: string | null;
7
+ }
8
+ declare const HostShell: import("svelte").Component<Props, {}, "">;
9
+ type HostShell = ReturnType<typeof HostShell>;
10
+ export default HostShell;
11
+ //# sourceMappingURL=HostShell.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostShell.svelte.d.ts","sourceRoot":"","sources":["../../src/dev/HostShell.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAQ7C,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgGH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}