sh3-core 0.7.3 → 0.8.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.
- package/dist/__test__/fixtures.d.ts +12 -0
- package/dist/__test__/fixtures.js +62 -0
- package/dist/__test__/render.d.ts +3 -0
- package/dist/__test__/render.js +11 -0
- package/dist/__test__/reset.d.ts +14 -0
- package/dist/__test__/reset.js +34 -0
- package/dist/__test__/setup-dom.d.ts +1 -0
- package/dist/__test__/setup-dom.js +26 -0
- package/dist/__test__/smoke.test.d.ts +1 -0
- package/dist/__test__/smoke.test.js +28 -0
- package/dist/api.d.ts +15 -2
- package/dist/api.js +13 -1
- package/dist/app/store/StoreView.svelte +36 -7
- package/dist/app/store/storeShard.svelte.js +9 -3
- package/dist/app/store/verbs.js +8 -2
- package/dist/apps/lifecycle.d.ts +11 -0
- package/dist/apps/lifecycle.js +48 -11
- package/dist/apps/lifecycle.test.d.ts +1 -0
- package/dist/apps/lifecycle.test.js +309 -0
- package/dist/apps/registry.svelte.d.ts +2 -0
- package/dist/apps/registry.svelte.js +5 -0
- package/dist/apps/types.d.ts +24 -2
- package/dist/createShell.d.ts +2 -0
- package/dist/createShell.js +9 -7
- package/dist/documents/handle.js +5 -0
- package/dist/documents/index.d.ts +1 -0
- package/dist/documents/index.js +1 -0
- package/dist/documents/journal-hook.d.ts +6 -0
- package/dist/documents/journal-hook.js +16 -0
- package/dist/documents/sync/activate-integration.test.d.ts +1 -0
- package/dist/documents/sync/activate-integration.test.js +37 -0
- package/dist/documents/sync/components/DocumentSyncExplorer.svelte +99 -0
- package/dist/documents/sync/components/DocumentSyncExplorer.svelte.d.ts +15 -0
- package/dist/documents/sync/components/SyncGrantPicker.svelte +70 -0
- package/dist/documents/sync/components/SyncGrantPicker.svelte.d.ts +12 -0
- package/dist/documents/sync/conflicts.d.ts +30 -0
- package/dist/documents/sync/conflicts.js +77 -0
- package/dist/documents/sync/conflicts.test.d.ts +1 -0
- package/dist/documents/sync/conflicts.test.js +71 -0
- package/dist/documents/sync/engine.d.ts +19 -0
- package/dist/documents/sync/engine.js +188 -0
- package/dist/documents/sync/engine.test.d.ts +1 -0
- package/dist/documents/sync/engine.test.js +169 -0
- package/dist/documents/sync/handle.d.ts +11 -0
- package/dist/documents/sync/handle.js +79 -0
- package/dist/documents/sync/handle.test.d.ts +1 -0
- package/dist/documents/sync/handle.test.js +56 -0
- package/dist/documents/sync/hash.d.ts +1 -0
- package/dist/documents/sync/hash.js +13 -0
- package/dist/documents/sync/hash.test.d.ts +1 -0
- package/dist/documents/sync/hash.test.js +20 -0
- package/dist/documents/sync/index.d.ts +6 -0
- package/dist/documents/sync/index.js +12 -0
- package/dist/documents/sync/journal.d.ts +30 -0
- package/dist/documents/sync/journal.js +179 -0
- package/dist/documents/sync/journal.test.d.ts +1 -0
- package/dist/documents/sync/journal.test.js +87 -0
- package/dist/documents/sync/registry.d.ts +10 -0
- package/dist/documents/sync/registry.js +66 -0
- package/dist/documents/sync/registry.test.d.ts +1 -0
- package/dist/documents/sync/registry.test.js +42 -0
- package/dist/documents/sync/serialization.d.ts +5 -0
- package/dist/documents/sync/serialization.js +24 -0
- package/dist/documents/sync/serialization.test.d.ts +1 -0
- package/dist/documents/sync/serialization.test.js +26 -0
- package/dist/documents/sync/singleton.d.ts +11 -0
- package/dist/documents/sync/singleton.js +26 -0
- package/dist/documents/sync/tombstones.d.ts +19 -0
- package/dist/documents/sync/tombstones.js +58 -0
- package/dist/documents/sync/tombstones.test.d.ts +1 -0
- package/dist/documents/sync/tombstones.test.js +37 -0
- package/dist/documents/sync/types.d.ts +116 -0
- package/dist/documents/sync/types.js +27 -0
- package/dist/documents/sync/write-hook.test.d.ts +1 -0
- package/dist/documents/sync/write-hook.test.js +36 -0
- package/dist/env/client.d.ts +10 -5
- package/dist/env/client.js +12 -4
- package/dist/layout/LayoutRenderer.browser.test.d.ts +1 -0
- package/dist/layout/LayoutRenderer.browser.test.js +274 -0
- package/dist/layout/LayoutRenderer.svelte +2 -1
- package/dist/layout/LayoutRenderer.test.d.ts +1 -0
- package/dist/layout/LayoutRenderer.test.js +143 -0
- package/dist/layout/SlotContainer.svelte +8 -2
- package/dist/layout/SlotDropZone.svelte +19 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-1-drag-tab-between-groups-moves-a-tab-from-one-tabs-group-to-another-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-2-drag-tab-to-quadrant-creates-a-split-when-dropping-a-tab-on-a-quadrant-drop-zone-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-3-splitter-drag-updates-split-sizes-when-the-splitter-handle-is-dragged-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-4-close-policy-removes-closable-tabs--keeps-non-closable--and-awaits-canClose-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-5-splitter-collapse-toggle-toggles-collapsed-i--on-double-click-1.png +0 -0
- package/dist/layout/drag.svelte.d.ts +5 -0
- package/dist/layout/drag.svelte.js +15 -0
- package/dist/layout/slotHostPool.svelte.d.ts +16 -1
- package/dist/layout/slotHostPool.svelte.js +123 -5
- package/dist/layout/slotHostPool.test.d.ts +1 -0
- package/dist/layout/slotHostPool.test.js +104 -0
- package/dist/layout/store.svelte.d.ts +22 -0
- package/dist/layout/store.svelte.js +78 -16
- package/dist/layout/tree-walk.d.ts +2 -0
- package/dist/layout/tree-walk.js +1 -1
- package/dist/layout/types.d.ts +5 -0
- package/dist/overlays/float.d.ts +2 -0
- package/dist/overlays/float.js +4 -1
- package/dist/overlays/float.test.js +102 -1
- package/dist/primitives/ResizableSplitter.svelte +2 -0
- package/dist/primitives/TabbedPanel.svelte +4 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +2 -0
- package/dist/registry/installer.d.ts +10 -7
- package/dist/registry/installer.js +39 -35
- package/dist/registry/register.d.ts +17 -0
- package/dist/registry/register.js +22 -0
- package/dist/registry/register.test.d.ts +1 -0
- package/dist/registry/register.test.js +28 -0
- package/dist/shards/activate.svelte.d.ts +6 -0
- package/dist/shards/activate.svelte.js +33 -2
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +18 -0
- package/dist/shards/types.d.ts +16 -1
- package/dist/shell-shard/Terminal.svelte +140 -33
- package/dist/shell-shard/Terminal.svelte.d.ts +3 -0
- package/dist/shell-shard/auto-relocate.d.ts +12 -0
- package/dist/shell-shard/auto-relocate.js +20 -0
- package/dist/shell-shard/auto-relocate.test.d.ts +1 -0
- package/dist/shell-shard/auto-relocate.test.js +35 -0
- package/dist/shell-shard/dispatch.d.ts +15 -0
- package/dist/shell-shard/dispatch.js +56 -0
- package/dist/shell-shard/modes/builtin.d.ts +5 -0
- package/dist/shell-shard/modes/builtin.js +18 -0
- package/dist/shell-shard/modes/prefs.d.ts +5 -0
- package/dist/shell-shard/modes/prefs.js +31 -0
- package/dist/shell-shard/modes/prefs.test.d.ts +1 -0
- package/dist/shell-shard/modes/prefs.test.js +46 -0
- package/dist/shell-shard/modes/registry.d.ts +7 -0
- package/dist/shell-shard/modes/registry.js +27 -0
- package/dist/shell-shard/modes/registry.test.d.ts +1 -0
- package/dist/shell-shard/modes/registry.test.js +35 -0
- package/dist/shell-shard/modes/types.d.ts +8 -0
- package/dist/shell-shard/modes/types.js +1 -0
- package/dist/shell-shard/protocol.d.ts +6 -0
- package/dist/shell-shard/shellShard.svelte.js +5 -1
- package/dist/shell-shard/tenant-fs-client.d.ts +24 -0
- package/dist/shell-shard/tenant-fs-client.js +44 -0
- package/dist/shell-shard/tenant-fs-client.test.d.ts +1 -0
- package/dist/shell-shard/tenant-fs-client.test.js +49 -0
- package/dist/shell-shard/terminal-dispatch.test.d.ts +1 -0
- package/dist/shell-shard/terminal-dispatch.test.js +53 -0
- package/dist/shell-shard/toolbar/Toolbar.svelte +62 -0
- package/dist/shell-shard/toolbar/Toolbar.svelte.d.ts +11 -0
- package/dist/shell-shard/toolbar/slots/FocusLockSlot.svelte +28 -0
- package/dist/shell-shard/toolbar/slots/FocusLockSlot.svelte.d.ts +7 -0
- package/dist/shell-shard/toolbar/slots/ModeSlot.svelte +102 -0
- package/dist/shell-shard/toolbar/slots/ModeSlot.svelte.d.ts +11 -0
- package/dist/shell-shard/toolbar/slots/TargetShardSlot.svelte +17 -0
- package/dist/shell-shard/toolbar/slots/TargetShardSlot.svelte.d.ts +6 -0
- package/dist/shell-shard/toolbar/slots.d.ts +17 -0
- package/dist/shell-shard/toolbar/slots.js +26 -0
- package/dist/shell-shard/toolbar/slots.test.d.ts +1 -0
- package/dist/shell-shard/toolbar/slots.test.js +28 -0
- package/dist/shell-shard/verbs/cat.d.ts +2 -0
- package/dist/shell-shard/verbs/cat.js +34 -0
- package/dist/shell-shard/verbs/cd.test.d.ts +1 -0
- package/dist/shell-shard/verbs/cd.test.js +56 -0
- package/dist/shell-shard/verbs/env.d.ts +2 -0
- package/dist/shell-shard/verbs/env.js +14 -0
- package/dist/shell-shard/verbs/index.js +6 -1
- package/dist/shell-shard/verbs/ls.d.ts +2 -0
- package/dist/shell-shard/verbs/ls.js +29 -0
- package/dist/shell-shard/verbs/ls.test.d.ts +1 -0
- package/dist/shell-shard/verbs/ls.test.js +49 -0
- package/dist/shell-shard/verbs/session.d.ts +0 -1
- package/dist/shell-shard/verbs/session.js +58 -26
- package/dist/verbs/types.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +9 -1
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
closable,
|
|
77
77
|
dirty,
|
|
78
78
|
onClose,
|
|
79
|
+
tabIds,
|
|
79
80
|
}: {
|
|
80
81
|
labels: string[];
|
|
81
82
|
icons?: (string | undefined)[];
|
|
@@ -100,6 +101,8 @@
|
|
|
100
101
|
dirty?: (boolean | undefined)[];
|
|
101
102
|
/** Called when the user clicks a tab's close button. */
|
|
102
103
|
onClose?: (index: number) => void;
|
|
104
|
+
/** Optional stable ids for each tab (e.g. slotId). Used as data-testid suffixes on close buttons. */
|
|
105
|
+
tabIds?: (string | undefined)[];
|
|
103
106
|
} = $props();
|
|
104
107
|
|
|
105
108
|
function select(i: number) {
|
|
@@ -172,6 +175,7 @@
|
|
|
172
175
|
role="button"
|
|
173
176
|
tabindex="-1"
|
|
174
177
|
title="Close"
|
|
178
|
+
data-testid={tabIds?.[i] ? `tab-close-${tabIds[i]}` : undefined}
|
|
175
179
|
onclick={(e) => handleClose(i, e)}
|
|
176
180
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleClose(i, e); }}
|
|
177
181
|
>✕</span>
|
|
@@ -44,6 +44,8 @@ type $$ComponentProps = {
|
|
|
44
44
|
dirty?: (boolean | undefined)[];
|
|
45
45
|
/** Called when the user clicks a tab's close button. */
|
|
46
46
|
onClose?: (index: number) => void;
|
|
47
|
+
/** Optional stable ids for each tab (e.g. slotId). Used as data-testid suffixes on close buttons. */
|
|
48
|
+
tabIds?: (string | undefined)[];
|
|
47
49
|
};
|
|
48
50
|
declare const TabbedPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
49
51
|
type TabbedPanel = ReturnType<typeof TabbedPanel>;
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Flow:
|
|
9
9
|
* 1. `installPackage(bundle, meta)` -- load module from bytes, verify
|
|
10
|
-
* declared type matches actual type, persist to IndexedDB,
|
|
11
|
-
*
|
|
12
|
-
* 2. `uninstallPackage(id)` -- deactivate if active,
|
|
10
|
+
* declared type matches actual type, persist to IndexedDB, evict any
|
|
11
|
+
* existing registration, then register via the shared helper.
|
|
12
|
+
* 2. `uninstallPackage(id)` -- deactivate if active, unregister app, remove
|
|
13
|
+
* from storage.
|
|
13
14
|
* 3. `loadInstalledPackages()` -- called at boot, re-loads all installed
|
|
14
15
|
* packages from IndexedDB and registers them.
|
|
15
16
|
*/
|
|
@@ -18,9 +19,10 @@ import type { InstalledPackage, InstallResult, PackageMeta } from './types';
|
|
|
18
19
|
* Install a package from raw bundle bytes and metadata.
|
|
19
20
|
*
|
|
20
21
|
* Loads the ESM module, verifies the declared type matches the actual module
|
|
21
|
-
* shape, persists to IndexedDB,
|
|
22
|
-
*
|
|
23
|
-
* glob-discovered), the package is still
|
|
22
|
+
* shape, persists to IndexedDB, evicts any existing registration for the same
|
|
23
|
+
* id, and registers with the framework via the shared helper. If registration
|
|
24
|
+
* fails (e.g. a shard that was already glob-discovered), the package is still
|
|
25
|
+
* persisted but `hotLoaded` is false.
|
|
24
26
|
*
|
|
25
27
|
* @param bundle - Raw verified ESM bundle bytes.
|
|
26
28
|
* @param meta - Provenance metadata for the install record.
|
|
@@ -31,7 +33,8 @@ export declare function installPackage(bundle: ArrayBuffer, meta: PackageMeta):
|
|
|
31
33
|
* Uninstall a package by id.
|
|
32
34
|
*
|
|
33
35
|
* If the package is a shard and currently active, deactivates it first.
|
|
34
|
-
*
|
|
36
|
+
* Unregisters any app entry for the id, then removes both the bundle and
|
|
37
|
+
* metadata from IndexedDB.
|
|
35
38
|
*
|
|
36
39
|
* @param id - The package id to uninstall.
|
|
37
40
|
*/
|
|
@@ -7,24 +7,27 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Flow:
|
|
9
9
|
* 1. `installPackage(bundle, meta)` -- load module from bytes, verify
|
|
10
|
-
* declared type matches actual type, persist to IndexedDB,
|
|
11
|
-
*
|
|
12
|
-
* 2. `uninstallPackage(id)` -- deactivate if active,
|
|
10
|
+
* declared type matches actual type, persist to IndexedDB, evict any
|
|
11
|
+
* existing registration, then register via the shared helper.
|
|
12
|
+
* 2. `uninstallPackage(id)` -- deactivate if active, unregister app, remove
|
|
13
|
+
* from storage.
|
|
13
14
|
* 3. `loadInstalledPackages()` -- called at boot, re-loads all installed
|
|
14
15
|
* packages from IndexedDB and registers them.
|
|
15
16
|
*/
|
|
16
17
|
import { loadBundleModule } from './loader';
|
|
17
18
|
import { savePackage, loadBundle, listInstalled, removePackage } from './storage';
|
|
18
19
|
import { verifyIntegrity } from './integrity';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import { deactivateShard } from '../shards/activate.svelte';
|
|
21
|
+
import { unregisterApp } from '../apps/lifecycle';
|
|
22
|
+
import { registerLoadedBundle } from './register';
|
|
21
23
|
/**
|
|
22
24
|
* Install a package from raw bundle bytes and metadata.
|
|
23
25
|
*
|
|
24
26
|
* Loads the ESM module, verifies the declared type matches the actual module
|
|
25
|
-
* shape, persists to IndexedDB,
|
|
26
|
-
*
|
|
27
|
-
* glob-discovered), the package is still
|
|
27
|
+
* shape, persists to IndexedDB, evicts any existing registration for the same
|
|
28
|
+
* id, and registers with the framework via the shared helper. If registration
|
|
29
|
+
* fails (e.g. a shard that was already glob-discovered), the package is still
|
|
30
|
+
* persisted but `hotLoaded` is false.
|
|
28
31
|
*
|
|
29
32
|
* @param bundle - Raw verified ESM bundle bytes.
|
|
30
33
|
* @param meta - Provenance metadata for the install record.
|
|
@@ -88,20 +91,27 @@ export async function installPackage(bundle, meta) {
|
|
|
88
91
|
error: `Failed to persist package: ${err instanceof Error ? err.message : String(err)}`,
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
|
-
// 5.
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
94
|
+
// 5. Evict any existing registration for this id before re-registering.
|
|
95
|
+
// Without this, reinstall at the same version leaks activation state
|
|
96
|
+
// (shards stay active) or app entries (apps silently replace but the
|
|
97
|
+
// old module instance's module-scope state is never torn down).
|
|
98
|
+
if (meta.type === 'shard' || meta.type === 'combo') {
|
|
99
|
+
try {
|
|
100
|
+
deactivateShard(meta.id);
|
|
101
|
+
}
|
|
102
|
+
catch ( /* not active or not a shard */_a) { /* not active or not a shard */ }
|
|
103
|
+
}
|
|
104
|
+
if (meta.type === 'app' || meta.type === 'combo') {
|
|
105
|
+
unregisterApp(meta.id);
|
|
106
|
+
}
|
|
107
|
+
// 6. Register all shards and apps from the bundle via the shared helper.
|
|
95
108
|
let hotLoaded = true;
|
|
96
109
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
app.manifest = Object.assign(Object.assign({}, app.manifest), { version: meta.version });
|
|
103
|
-
registerApp(app);
|
|
104
|
-
}
|
|
110
|
+
registerLoadedBundle(loaded, {
|
|
111
|
+
version: meta.version,
|
|
112
|
+
sourceRegistry: meta.sourceRegistry,
|
|
113
|
+
contractVersion: meta.contractVersion,
|
|
114
|
+
});
|
|
105
115
|
}
|
|
106
116
|
catch (err) {
|
|
107
117
|
console.warn(`[sh3] Package "${meta.id}" installed but registration failed (will retry on next boot):`, err instanceof Error ? err.message : err);
|
|
@@ -113,18 +123,17 @@ export async function installPackage(bundle, meta) {
|
|
|
113
123
|
* Uninstall a package by id.
|
|
114
124
|
*
|
|
115
125
|
* If the package is a shard and currently active, deactivates it first.
|
|
116
|
-
*
|
|
126
|
+
* Unregisters any app entry for the id, then removes both the bundle and
|
|
127
|
+
* metadata from IndexedDB.
|
|
117
128
|
*
|
|
118
129
|
* @param id - The package id to uninstall.
|
|
119
130
|
*/
|
|
120
131
|
export async function uninstallPackage(id) {
|
|
121
|
-
// Attempt deactivation (no-op if not active or not a shard).
|
|
122
132
|
try {
|
|
123
133
|
deactivateShard(id);
|
|
124
134
|
}
|
|
125
|
-
catch (_a) {
|
|
126
|
-
|
|
127
|
-
}
|
|
135
|
+
catch ( /* no-op */_a) { /* no-op */ }
|
|
136
|
+
unregisterApp(id);
|
|
128
137
|
await removePackage(id);
|
|
129
138
|
}
|
|
130
139
|
/**
|
|
@@ -160,16 +169,11 @@ export async function loadInstalledPackages() {
|
|
|
160
169
|
continue;
|
|
161
170
|
}
|
|
162
171
|
const loaded = await loadBundleModule(bytes);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
for (const app of loaded.apps) {
|
|
170
|
-
app.manifest = Object.assign(Object.assign({}, app.manifest), { version: pkg.version });
|
|
171
|
-
registerApp(app);
|
|
172
|
-
}
|
|
172
|
+
registerLoadedBundle(loaded, {
|
|
173
|
+
version: pkg.version,
|
|
174
|
+
sourceRegistry: pkg.sourceRegistry,
|
|
175
|
+
contractVersion: pkg.contractVersion,
|
|
176
|
+
});
|
|
173
177
|
if (loaded.shards.length === 0 && loaded.apps.length === 0) {
|
|
174
178
|
console.warn(`[sh3] Package "${pkg.id}" contains no valid shards or apps, skipping`);
|
|
175
179
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified bundle registration — stamps loader-assigned metadata onto every
|
|
3
|
+
* shard/app manifest from a loaded bundle, then registers each. Shared by
|
|
4
|
+
* `installer.installPackage`, `installer.loadInstalledPackages`, and
|
|
5
|
+
* `createShell` discoveredPackages so the three boot paths stay in sync.
|
|
6
|
+
*
|
|
7
|
+
* Per ADR-013, external package authors omit `version` from their source
|
|
8
|
+
* manifests; the authoritative value comes from the persisted/server
|
|
9
|
+
* metadata and must be stamped here before any consumer reads the manifest.
|
|
10
|
+
*/
|
|
11
|
+
import type { LoadedBundle } from './loader';
|
|
12
|
+
export interface BundleStampMeta {
|
|
13
|
+
version: string;
|
|
14
|
+
sourceRegistry: string;
|
|
15
|
+
contractVersion: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function registerLoadedBundle(loaded: LoadedBundle, meta: BundleStampMeta): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified bundle registration — stamps loader-assigned metadata onto every
|
|
3
|
+
* shard/app manifest from a loaded bundle, then registers each. Shared by
|
|
4
|
+
* `installer.installPackage`, `installer.loadInstalledPackages`, and
|
|
5
|
+
* `createShell` discoveredPackages so the three boot paths stay in sync.
|
|
6
|
+
*
|
|
7
|
+
* Per ADR-013, external package authors omit `version` from their source
|
|
8
|
+
* manifests; the authoritative value comes from the persisted/server
|
|
9
|
+
* metadata and must be stamped here before any consumer reads the manifest.
|
|
10
|
+
*/
|
|
11
|
+
import { registerShard } from '../shards/activate.svelte';
|
|
12
|
+
import { registerApp } from '../apps/registry.svelte';
|
|
13
|
+
export function registerLoadedBundle(loaded, meta) {
|
|
14
|
+
for (const shard of loaded.shards) {
|
|
15
|
+
shard.manifest = Object.assign(Object.assign({}, shard.manifest), { version: meta.version });
|
|
16
|
+
registerShard(shard);
|
|
17
|
+
}
|
|
18
|
+
for (const app of loaded.apps) {
|
|
19
|
+
app.manifest = Object.assign(Object.assign({}, app.manifest), { version: meta.version });
|
|
20
|
+
registerApp(app);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { resetFramework } from '../__test__/reset';
|
|
3
|
+
import { makeApp, makeAppManifest, makeShard, makeShardManifest } from '../__test__/fixtures';
|
|
4
|
+
import { registerLoadedBundle } from './register';
|
|
5
|
+
import { registeredApps } from '../apps/registry.svelte';
|
|
6
|
+
import { registeredShards } from '../shards/activate.svelte';
|
|
7
|
+
describe('registerLoadedBundle', () => {
|
|
8
|
+
beforeEach(resetFramework);
|
|
9
|
+
it('stamps meta.version onto every shard manifest before registering', () => {
|
|
10
|
+
var _a;
|
|
11
|
+
const shard = makeShard({ manifest: makeShardManifest({ id: 'shard-1', version: '' }) });
|
|
12
|
+
registerLoadedBundle({ shards: [shard], apps: [] }, { version: '2.3.4', sourceRegistry: 'https://r', contractVersion: '1' });
|
|
13
|
+
expect((_a = registeredShards.get('shard-1')) === null || _a === void 0 ? void 0 : _a.manifest.version).toBe('2.3.4');
|
|
14
|
+
});
|
|
15
|
+
it('stamps meta.version onto every app manifest before registering', () => {
|
|
16
|
+
var _a;
|
|
17
|
+
const app = makeApp({ manifest: makeAppManifest({ id: 'app-1', version: '' }) });
|
|
18
|
+
registerLoadedBundle({ shards: [], apps: [app] }, { version: '2.3.4', sourceRegistry: 'https://r', contractVersion: '1' });
|
|
19
|
+
expect((_a = registeredApps.get('app-1')) === null || _a === void 0 ? void 0 : _a.manifest.version).toBe('2.3.4');
|
|
20
|
+
});
|
|
21
|
+
it('registers combo bundles (both shards and apps)', () => {
|
|
22
|
+
const shard = makeShard({ manifest: makeShardManifest({ id: 'combo-s' }) });
|
|
23
|
+
const app = makeApp({ manifest: makeAppManifest({ id: 'combo-a', requiredShards: ['combo-s'] }) });
|
|
24
|
+
registerLoadedBundle({ shards: [shard], apps: [app] }, { version: '1.0.0', sourceRegistry: '', contractVersion: '1' });
|
|
25
|
+
expect(registeredShards.has('combo-s')).toBe(true);
|
|
26
|
+
expect(registeredApps.has('combo-a')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -50,3 +50,9 @@ export declare function isActive(id: string): boolean;
|
|
|
50
50
|
* Used by lifecycle.ts to pass context to `shard.resume()`.
|
|
51
51
|
*/
|
|
52
52
|
export declare function getShardContext(id: string): ShardContext | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Test-only reset. Tears down any active shard entries (without running
|
|
55
|
+
* deactivate hooks — tests should run deactivate explicitly if they care)
|
|
56
|
+
* and clears both registered and active maps.
|
|
57
|
+
*/
|
|
58
|
+
export declare function __resetShardRegistryForTest(): void;
|
|
@@ -23,6 +23,9 @@ import { fetchEnvState, putEnvState } from '../env/client';
|
|
|
23
23
|
import { isAdmin as checkIsAdmin } from '../auth/index';
|
|
24
24
|
import { createZoneManager } from '../state/manage';
|
|
25
25
|
import { PERMISSION_STATE_MANAGE } from '../state/types';
|
|
26
|
+
import { PERMISSION_DOCUMENTS_SYNC } from '../documents/sync/types';
|
|
27
|
+
import { getSyncBundle } from '../documents/sync/singleton';
|
|
28
|
+
import { createSyncHandle } from '../documents/sync/handle';
|
|
26
29
|
/**
|
|
27
30
|
* Reactive registry of every shard known to the host. Keys are shard ids.
|
|
28
31
|
* Populated once at boot by the glob-discovery loop in main.ts (through
|
|
@@ -65,7 +68,7 @@ export function registerShard(shard) {
|
|
|
65
68
|
* @throws If the shard is not registered, or if a manifest view has no factory after activation.
|
|
66
69
|
*/
|
|
67
70
|
export async function activateShard(id) {
|
|
68
|
-
var _a, _b;
|
|
71
|
+
var _a, _b, _c;
|
|
69
72
|
const shard = registeredShards.get(id);
|
|
70
73
|
if (!shard) {
|
|
71
74
|
throw new Error(`Cannot activate shard "${id}": not registered`);
|
|
@@ -128,6 +131,24 @@ export async function activateShard(id) {
|
|
|
128
131
|
zones: ((_a = shard.manifest.permissions) === null || _a === void 0 ? void 0 : _a.includes(PERMISSION_STATE_MANAGE))
|
|
129
132
|
? createZoneManager()
|
|
130
133
|
: undefined,
|
|
134
|
+
sync: ((_b = shard.manifest.permissions) === null || _b === void 0 ? void 0 : _b.includes(PERMISSION_DOCUMENTS_SYNC))
|
|
135
|
+
? () => {
|
|
136
|
+
const backend = getDocumentBackend();
|
|
137
|
+
const tenantId = getTenantId();
|
|
138
|
+
const bundlePromise = getSyncBundle(backend, tenantId);
|
|
139
|
+
const handlePromise = bundlePromise.then(({ engine, registry }) => createSyncHandle({ tenantId, connectorId: id, engine, registry }));
|
|
140
|
+
return {
|
|
141
|
+
connectorId: id,
|
|
142
|
+
grantedScopes: async () => (await handlePromise).grantedScopes(),
|
|
143
|
+
getManifest: async (scope) => (await handlePromise).getManifest(scope),
|
|
144
|
+
changesSince: async (scope, cursor) => (await handlePromise).changesSince(scope, cursor),
|
|
145
|
+
ack: async (scope, cursor) => (await handlePromise).ack(scope, cursor),
|
|
146
|
+
apply: async (scope, entry, opts) => (await handlePromise).apply(scope, entry, opts),
|
|
147
|
+
applyBatch: async (scope, manifest, opts) => (await handlePromise).applyBatch(scope, manifest, opts),
|
|
148
|
+
forget: async (scope, path) => (await handlePromise).forget(scope, path),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
: undefined,
|
|
131
152
|
};
|
|
132
153
|
entry.ctx = ctx;
|
|
133
154
|
active.set(id, entry);
|
|
@@ -149,7 +170,7 @@ export async function activateShard(id) {
|
|
|
149
170
|
console.warn(`[sh3] Failed to hydrate env state for shard "${id}":`, err instanceof Error ? err.message : err);
|
|
150
171
|
}
|
|
151
172
|
}
|
|
152
|
-
void ((
|
|
173
|
+
void ((_c = shard.autostart) === null || _c === void 0 ? void 0 : _c.call(shard, ctx));
|
|
153
174
|
}
|
|
154
175
|
/**
|
|
155
176
|
* Deactivate an active shard. Calls `shard.deactivate`, flushes and disposes
|
|
@@ -191,3 +212,13 @@ export function getShardContext(id) {
|
|
|
191
212
|
var _a;
|
|
192
213
|
return (_a = active.get(id)) === null || _a === void 0 ? void 0 : _a.ctx;
|
|
193
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Test-only reset. Tears down any active shard entries (without running
|
|
217
|
+
* deactivate hooks — tests should run deactivate explicitly if they care)
|
|
218
|
+
* and clears both registered and active maps.
|
|
219
|
+
*/
|
|
220
|
+
export function __resetShardRegistryForTest() {
|
|
221
|
+
active.clear();
|
|
222
|
+
activeShards.clear();
|
|
223
|
+
registeredShards.clear();
|
|
224
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ViewFactory } from './types';
|
|
2
|
+
export declare function __addViewRegistrationListener(fn: (viewId: string, factory: ViewFactory) => void): void;
|
|
3
|
+
export declare function __removeViewRegistrationListener(fn: (viewId: string, factory: ViewFactory) => void): void;
|
|
2
4
|
export declare function registerView(viewId: string, factory: ViewFactory): void;
|
|
3
5
|
export declare function getView(viewId: string): ViewFactory | undefined;
|
|
4
6
|
export declare function unregisterView(viewId: string): void;
|
|
@@ -7,3 +9,5 @@ export declare function registerVerb(name: string, verb: Verb): void;
|
|
|
7
9
|
export declare function getVerb(name: string): Verb | undefined;
|
|
8
10
|
export declare function unregisterVerb(name: string): void;
|
|
9
11
|
export declare function listVerbs(): Verb[];
|
|
12
|
+
/** Test-only reset: clear the view and verb registries. */
|
|
13
|
+
export declare function __resetViewRegistryForTest(): void;
|
package/dist/shards/registry.js
CHANGED
|
@@ -14,11 +14,22 @@
|
|
|
14
14
|
* hotkeys get their own sibling maps when those kinds land.
|
|
15
15
|
*/
|
|
16
16
|
const views = new Map();
|
|
17
|
+
/** Listeners called after a new view factory is registered. */
|
|
18
|
+
const viewRegistrationListeners = new Set();
|
|
19
|
+
export function __addViewRegistrationListener(fn) {
|
|
20
|
+
viewRegistrationListeners.add(fn);
|
|
21
|
+
}
|
|
22
|
+
export function __removeViewRegistrationListener(fn) {
|
|
23
|
+
viewRegistrationListeners.delete(fn);
|
|
24
|
+
}
|
|
17
25
|
export function registerView(viewId, factory) {
|
|
18
26
|
if (views.has(viewId)) {
|
|
19
27
|
throw new Error(`View "${viewId}" is already registered`);
|
|
20
28
|
}
|
|
21
29
|
views.set(viewId, factory);
|
|
30
|
+
for (const listener of viewRegistrationListeners) {
|
|
31
|
+
listener(viewId, factory);
|
|
32
|
+
}
|
|
22
33
|
}
|
|
23
34
|
export function getView(viewId) {
|
|
24
35
|
return views.get(viewId);
|
|
@@ -42,3 +53,10 @@ export function unregisterVerb(name) {
|
|
|
42
53
|
export function listVerbs() {
|
|
43
54
|
return Array.from(verbs.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
44
55
|
}
|
|
56
|
+
/** Test-only reset: clear the view and verb registries. */
|
|
57
|
+
export function __resetViewRegistryForTest() {
|
|
58
|
+
views.clear();
|
|
59
|
+
verbs.clear();
|
|
60
|
+
// Do NOT clear viewRegistrationListeners — they are module-level subscriptions
|
|
61
|
+
// (e.g. slotHostPool's late-factory listener) that must survive registry resets.
|
|
62
|
+
}
|
package/dist/shards/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StateZones } from '../state/zones.svelte';
|
|
2
2
|
import type { ZoneSchema, ZoneManager } from '../state/types';
|
|
3
3
|
import type { DocumentHandle, DocumentHandleOptions } from '../documents/types';
|
|
4
|
+
import type { SyncHandle } from '../documents/sync/types';
|
|
4
5
|
import type { EnvState } from '../env/types';
|
|
5
6
|
import type { Verb } from '../verbs/types';
|
|
6
7
|
/**
|
|
@@ -43,6 +44,12 @@ export interface MountContext {
|
|
|
43
44
|
viewId: string;
|
|
44
45
|
/** Initial label for the tab; may be updated by the view via future API. */
|
|
45
46
|
label: string;
|
|
47
|
+
/**
|
|
48
|
+
* Caller-supplied instance data. Present when the mount was triggered with
|
|
49
|
+
* metadata (e.g. `shell.float.open(viewId, { meta: { ... } })`).
|
|
50
|
+
* Not persisted with the layout — ephemeral per mount.
|
|
51
|
+
*/
|
|
52
|
+
meta?: Record<string, unknown>;
|
|
46
53
|
/**
|
|
47
54
|
* Push dirty-state to the tab strip. The framework renders a dirty
|
|
48
55
|
* indicator (filled dot) on the tab when true, clears it when false.
|
|
@@ -110,7 +117,9 @@ export interface ShardManifest {
|
|
|
110
117
|
/**
|
|
111
118
|
* Optional permissions this shard requests beyond the default sandbox.
|
|
112
119
|
* Declared in the manifest and surfaced to the user at install time.
|
|
113
|
-
* Currently recognized:
|
|
120
|
+
* Currently recognized:
|
|
121
|
+
* - 'state:manage' — cross-shard zone access.
|
|
122
|
+
* - 'documents:sync' — cross-shard document sync API.
|
|
114
123
|
*/
|
|
115
124
|
permissions?: string[];
|
|
116
125
|
}
|
|
@@ -185,6 +194,12 @@ export interface ShardContext {
|
|
|
185
194
|
* `if (ctx.zones)` before use.
|
|
186
195
|
*/
|
|
187
196
|
zones?: ZoneManager;
|
|
197
|
+
/**
|
|
198
|
+
* Cross-shard document sync API. Only present when the shard's
|
|
199
|
+
* manifest declares the `'documents:sync'` permission. Check with
|
|
200
|
+
* `if (ctx.sync)` before use.
|
|
201
|
+
*/
|
|
202
|
+
sync?: () => SyncHandle;
|
|
188
203
|
}
|
|
189
204
|
/**
|
|
190
205
|
* A shard module. Shards are the fundamental unit of contribution in SH3.
|
|
@@ -6,55 +6,149 @@
|
|
|
6
6
|
import { SessionClient } from './session-client.svelte';
|
|
7
7
|
import { VerbRegistry, type ShellApi } from './registry';
|
|
8
8
|
import type { ServerMessage } from './protocol';
|
|
9
|
+
import { TenantFsClient } from './tenant-fs-client';
|
|
10
|
+
import { ShellModeRegistry } from './modes/registry';
|
|
11
|
+
import { registerBuiltinModes } from './modes/builtin';
|
|
12
|
+
import { resolveInitialMode, writeLastMode } from './modes/prefs';
|
|
13
|
+
import type { ShellMode, ShellRole } from './modes/types';
|
|
14
|
+
import { makeDispatch } from './dispatch';
|
|
15
|
+
import { computeRelocate } from './auto-relocate';
|
|
16
|
+
import { activeLayout } from '../layout/store.svelte';
|
|
17
|
+
import type { LayoutNode } from '../layout/types';
|
|
18
|
+
import Toolbar from './toolbar/Toolbar.svelte';
|
|
19
|
+
import { ToolbarSlotRegistry } from './toolbar/slots';
|
|
20
|
+
import ModeSlot from './toolbar/slots/ModeSlot.svelte';
|
|
21
|
+
import FocusLockSlot from './toolbar/slots/FocusLockSlot.svelte';
|
|
22
|
+
import TargetShardSlot from './toolbar/slots/TargetShardSlot.svelte';
|
|
9
23
|
|
|
10
24
|
interface Props {
|
|
11
25
|
shell: ShellApi;
|
|
12
26
|
wsUrl: string;
|
|
27
|
+
userId: string;
|
|
28
|
+
role: ShellRole;
|
|
13
29
|
}
|
|
14
|
-
let { shell, wsUrl }: Props = $props();
|
|
30
|
+
let { shell, wsUrl, userId, role }: Props = $props();
|
|
15
31
|
|
|
16
32
|
const scrollback = new Scrollback();
|
|
33
|
+
const resolver = new VerbRegistry();
|
|
34
|
+
const fs = new TenantFsClient();
|
|
35
|
+
|
|
36
|
+
// Mode registry
|
|
37
|
+
const modeRegistry = new ShellModeRegistry();
|
|
38
|
+
registerBuiltinModes(modeRegistry);
|
|
39
|
+
|
|
40
|
+
// Reactive current mode
|
|
41
|
+
let mode = $state<ShellMode>(
|
|
42
|
+
untrack(() => resolveInitialMode(modeRegistry, userId, role)),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function setMode(id: string): void {
|
|
46
|
+
const next = modeRegistry.get(id);
|
|
47
|
+
if (!next) return;
|
|
48
|
+
if (next.requiresRole && next.requiresRole !== role) return;
|
|
49
|
+
mode = next;
|
|
50
|
+
writeLastMode(userId, id);
|
|
51
|
+
if (next.transport !== 'ws') {
|
|
52
|
+
scrollback.push({ kind: 'status', text: 'mode switch: reload to take effect for server-shell changes', level: 'info', ts: Date.now() });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
17
56
|
// wsUrl is a prop read at construction only. untrack prevents Svelte 5's
|
|
18
57
|
// "referenced outside a closure" warning; the URL never changes at runtime.
|
|
19
58
|
const session = untrack(() => new SessionClient(wsUrl));
|
|
20
|
-
|
|
59
|
+
|
|
60
|
+
const dispatch = untrack(() => makeDispatch({
|
|
61
|
+
mode: () => mode,
|
|
62
|
+
resolver,
|
|
63
|
+
scrollback,
|
|
64
|
+
session,
|
|
65
|
+
shell,
|
|
66
|
+
fs,
|
|
67
|
+
cwd: () => session.cwd,
|
|
68
|
+
}));
|
|
21
69
|
|
|
22
70
|
let locked = $state(false);
|
|
23
71
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Auto-relocate: track the focused shard and update session.cwd when focus
|
|
74
|
+
// changes to a shard whose documents directory exists. focusLocked and
|
|
75
|
+
// targetShard are read by Task 13's toolbar component via props.
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
let focusLocked = $state(false);
|
|
79
|
+
let targetShard = $state<string | null>(null);
|
|
80
|
+
|
|
81
|
+
function toggleFocusLock(): void {
|
|
82
|
+
focusLocked = !focusLocked;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Toolbar slot registry
|
|
86
|
+
const toolbarRegistry = new ToolbarSlotRegistry();
|
|
87
|
+
toolbarRegistry.register({ id: 'mode', order: 10, visible: () => true, component: ModeSlot });
|
|
88
|
+
toolbarRegistry.register({ id: 'focus-lock', order: 20, visible: (ctx) => ctx.mode.id === 'user', component: FocusLockSlot });
|
|
89
|
+
toolbarRegistry.register({ id: 'target-shard', order: 30, visible: (ctx) => ctx.mode.id === 'user', component: TargetShardSlot });
|
|
90
|
+
|
|
91
|
+
let toolbarExpanded = $state((() => {
|
|
92
|
+
try { return localStorage.getItem('sh3.shell.toolbarExpanded') !== '0'; } catch { return true; }
|
|
93
|
+
})());
|
|
94
|
+
|
|
95
|
+
function toggleToolbar() {
|
|
96
|
+
toolbarExpanded = !toolbarExpanded;
|
|
97
|
+
try { localStorage.setItem('sh3.shell.toolbarExpanded', toolbarExpanded ? '1' : '0'); } catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Walk the layout tree and return the viewId of the active tab in the first
|
|
101
|
+
* TabsNode found (breadth-first). Returns null if the layout contains no
|
|
102
|
+
* tabs node with a populated active tab. */
|
|
103
|
+
function getActiveViewId(node: LayoutNode): string | null {
|
|
104
|
+
if (node.type === 'tabs') {
|
|
105
|
+
const entry = node.tabs[node.activeTab];
|
|
106
|
+
return entry?.viewId ?? null;
|
|
107
|
+
}
|
|
108
|
+
if (node.type === 'split') {
|
|
109
|
+
for (const child of node.children) {
|
|
110
|
+
const found = getActiveViewId(child);
|
|
111
|
+
if (found !== null) return found;
|
|
51
112
|
}
|
|
52
|
-
} else {
|
|
53
|
-
// Forward to server
|
|
54
|
-
session.send({ t: 'submit', line: resolution.line });
|
|
55
113
|
}
|
|
114
|
+
// slot node
|
|
115
|
+
return node.viewId ?? null;
|
|
56
116
|
}
|
|
57
117
|
|
|
118
|
+
/** Derive the focused shard id from the currently-active layout. The shard
|
|
119
|
+
* id is the prefix before the first ':' in a viewId (e.g. 'shell:terminal'
|
|
120
|
+
* → 'shell'). Returns null when no view is active. */
|
|
121
|
+
function getFocusedShardId(): string | null {
|
|
122
|
+
try {
|
|
123
|
+
const tree = activeLayout();
|
|
124
|
+
const viewId = getActiveViewId(tree.docked);
|
|
125
|
+
if (!viewId) return null;
|
|
126
|
+
const colon = viewId.indexOf(':');
|
|
127
|
+
return colon >= 0 ? viewId.slice(0, colon) : viewId;
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
$effect(() => {
|
|
134
|
+
const focused = getFocusedShardId();
|
|
135
|
+
const autoRelocate = mode.autoRelocate;
|
|
136
|
+
const isFocusLocked = focusLocked;
|
|
137
|
+
(async () => {
|
|
138
|
+
const r = await computeRelocate({
|
|
139
|
+
modeAutoRelocate: autoRelocate,
|
|
140
|
+
focusLocked: isFocusLocked,
|
|
141
|
+
focusedShardId: focused,
|
|
142
|
+
currentShardId: 'shell',
|
|
143
|
+
}, fs);
|
|
144
|
+
if (r.kind === 'relocate' && r.path !== undefined) {
|
|
145
|
+
session.cwd = r.path;
|
|
146
|
+
}
|
|
147
|
+
if (focused && focused !== 'shell') targetShard = focused;
|
|
148
|
+
else if (!focused) targetShard = null;
|
|
149
|
+
})();
|
|
150
|
+
});
|
|
151
|
+
|
|
58
152
|
function handleServerMessage(msg: ServerMessage) {
|
|
59
153
|
if (msg.t !== 'event') return;
|
|
60
154
|
const e = msg.event;
|
|
@@ -96,7 +190,9 @@
|
|
|
96
190
|
|
|
97
191
|
onMount(() => {
|
|
98
192
|
unsub = session.onMessage(handleServerMessage);
|
|
99
|
-
|
|
193
|
+
if (mode.transport === 'ws') {
|
|
194
|
+
session.connect();
|
|
195
|
+
}
|
|
100
196
|
});
|
|
101
197
|
|
|
102
198
|
onDestroy(() => {
|
|
@@ -106,6 +202,17 @@
|
|
|
106
202
|
</script>
|
|
107
203
|
|
|
108
204
|
<div class="shell-terminal">
|
|
205
|
+
<Toolbar
|
|
206
|
+
registry={toolbarRegistry}
|
|
207
|
+
ctx={{ mode, role }}
|
|
208
|
+
expanded={toolbarExpanded}
|
|
209
|
+
onToggle={toggleToolbar}
|
|
210
|
+
slotProps={{
|
|
211
|
+
mode: { mode, role, registry: modeRegistry, onSelect: setMode },
|
|
212
|
+
'focus-lock': { locked: focusLocked, onToggle: () => (focusLocked = !focusLocked) },
|
|
213
|
+
'target-shard': { target: targetShard },
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
109
216
|
<ScrollbackView {scrollback} />
|
|
110
217
|
<InputLine
|
|
111
218
|
cwd={session.cwd}
|