sh3-core 0.6.0 → 0.7.1

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 (92) hide show
  1. package/dist/Shell.svelte +20 -14
  2. package/dist/api.d.ts +7 -3
  3. package/dist/api.js +1 -0
  4. package/dist/app/admin/adminApp.js +2 -1
  5. package/dist/app/admin/adminShard.svelte.js +2 -1
  6. package/dist/app/store/StoreView.svelte +11 -5
  7. package/dist/app/store/storeApp.js +2 -1
  8. package/dist/app/store/storeShard.svelte.js +14 -4
  9. package/dist/app/store/verbs.d.ts +4 -0
  10. package/dist/app/store/verbs.js +220 -0
  11. package/dist/apps/terminal/manifest.js +2 -1
  12. package/dist/apps/types.d.ts +28 -7
  13. package/dist/build.d.ts +5 -2
  14. package/dist/build.js +21 -10
  15. package/dist/env/client.d.ts +10 -2
  16. package/dist/env/client.js +13 -2
  17. package/dist/layout/LayoutRenderer.svelte +21 -9
  18. package/dist/layout/LayoutRenderer.svelte.d.ts +2 -0
  19. package/dist/layout/SlotDropZone.svelte +4 -1
  20. package/dist/layout/SlotDropZone.svelte.d.ts +2 -0
  21. package/dist/layout/drag.svelte.d.ts +5 -2
  22. package/dist/layout/drag.svelte.js +43 -11
  23. package/dist/layout/floats.d.ts +35 -0
  24. package/dist/layout/floats.js +73 -0
  25. package/dist/layout/floats.test.d.ts +1 -0
  26. package/dist/layout/floats.test.js +114 -0
  27. package/dist/layout/inspection.d.ts +2 -2
  28. package/dist/layout/inspection.js +6 -6
  29. package/dist/layout/ops.d.ts +14 -1
  30. package/dist/layout/ops.js +17 -0
  31. package/dist/layout/ops.test.d.ts +1 -0
  32. package/dist/layout/ops.test.js +36 -0
  33. package/dist/layout/presets.d.ts +2 -0
  34. package/dist/layout/presets.js +49 -0
  35. package/dist/layout/presets.test.d.ts +1 -0
  36. package/dist/layout/presets.test.js +71 -0
  37. package/dist/layout/store.svelte.d.ts +17 -13
  38. package/dist/layout/store.svelte.js +98 -36
  39. package/dist/layout/tree-walk.d.ts +12 -1
  40. package/dist/layout/tree-walk.js +13 -0
  41. package/dist/layout/tree-walk.test.d.ts +1 -0
  42. package/dist/layout/tree-walk.test.js +41 -0
  43. package/dist/layout/types.d.ts +96 -6
  44. package/dist/layout/types.js +1 -1
  45. package/dist/overlays/FloatFrame.svelte +142 -0
  46. package/dist/overlays/FloatFrame.svelte.d.ts +7 -0
  47. package/dist/overlays/FloatLayer.svelte +28 -0
  48. package/dist/overlays/FloatLayer.svelte.d.ts +3 -0
  49. package/dist/overlays/float.d.ts +29 -0
  50. package/dist/overlays/float.js +119 -0
  51. package/dist/overlays/float.test.d.ts +1 -0
  52. package/dist/overlays/float.test.js +37 -0
  53. package/dist/overlays/presets.d.ts +21 -0
  54. package/dist/overlays/presets.js +63 -0
  55. package/dist/overlays/presets.test.d.ts +1 -0
  56. package/dist/overlays/presets.test.js +40 -0
  57. package/dist/registry/client.d.ts +14 -0
  58. package/dist/registry/client.js +37 -0
  59. package/dist/registry/client.test.d.ts +1 -0
  60. package/dist/registry/client.test.js +54 -0
  61. package/dist/registry/installer.js +18 -5
  62. package/dist/registry/schema.js +5 -0
  63. package/dist/registry/types.d.ts +9 -0
  64. package/dist/shards/activate.svelte.js +9 -2
  65. package/dist/shards/registry.d.ts +5 -0
  66. package/dist/shards/registry.js +19 -3
  67. package/dist/shards/registry.test.d.ts +1 -0
  68. package/dist/shards/registry.test.js +62 -0
  69. package/dist/shards/types.d.ts +36 -4
  70. package/dist/shell-shard/Terminal.svelte +17 -12
  71. package/dist/shell-shard/manifest.js +2 -1
  72. package/dist/shell-shard/registry.d.ts +2 -64
  73. package/dist/shell-shard/registry.js +9 -17
  74. package/dist/shell-shard/shellShard.svelte.js +4 -1
  75. package/dist/shell-shard/verbs/apps.d.ts +1 -1
  76. package/dist/shell-shard/verbs/clear.d.ts +1 -1
  77. package/dist/shell-shard/verbs/help.d.ts +2 -2
  78. package/dist/shell-shard/verbs/help.js +3 -2
  79. package/dist/shell-shard/verbs/history.d.ts +1 -1
  80. package/dist/shell-shard/verbs/index.d.ts +2 -2
  81. package/dist/shell-shard/verbs/index.js +18 -18
  82. package/dist/shell-shard/verbs/session.d.ts +1 -1
  83. package/dist/shell-shard/verbs/shards.d.ts +1 -1
  84. package/dist/shell-shard/verbs/views.d.ts +1 -1
  85. package/dist/shell-shard/verbs/zones.d.ts +1 -1
  86. package/dist/shellRuntime.svelte.d.ts +6 -0
  87. package/dist/shellRuntime.svelte.js +4 -0
  88. package/dist/verbs/types.d.ts +62 -0
  89. package/dist/verbs/types.js +8 -0
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/package.json +6 -3
@@ -119,6 +119,10 @@ function validatePackageVersion(data, path) {
119
119
  if ('serverBundleUrl' in obj && obj.serverBundleUrl !== undefined) {
120
120
  requireString(obj, 'serverBundleUrl', path);
121
121
  }
122
+ // Optional server bundle integrity hash — provisional, see ADR-015.
123
+ if ('serverIntegrity' in obj && obj.serverIntegrity !== undefined) {
124
+ requireString(obj, 'serverIntegrity', path);
125
+ }
122
126
  let requires;
123
127
  if (obj['requires'] !== undefined) {
124
128
  if (!Array.isArray(obj['requires'])) {
@@ -132,6 +136,7 @@ function validatePackageVersion(data, path) {
132
136
  bundleUrl: obj['bundleUrl'],
133
137
  integrity: obj['integrity'],
134
138
  serverBundleUrl: typeof obj['serverBundleUrl'] === 'string' ? obj['serverBundleUrl'] : undefined,
139
+ serverIntegrity: typeof obj['serverIntegrity'] === 'string' ? obj['serverIntegrity'] : undefined,
135
140
  requires,
136
141
  };
137
142
  }
@@ -117,6 +117,15 @@ export interface PackageVersion {
117
117
  * manifest.
118
118
  */
119
119
  serverBundleUrl?: string;
120
+ /**
121
+ * SRI integrity hash for the server bundle. Same format as `integrity`.
122
+ * Optional in contract v1 for back-compat with registries that predate
123
+ * server bundles. Will become required when the formal registry spec
124
+ * lands (see ADR-015 proposal). When absent, the client skips the SRI
125
+ * check at download time and logs a warning — the unverified bundle is
126
+ * still installed.
127
+ */
128
+ serverIntegrity?: string;
120
129
  /**
121
130
  * Other shards that must be installed and active before this package
122
131
  * can be loaded. Optional — omit if the package has no dependencies.
@@ -17,7 +17,7 @@
17
17
  * stays in `registeredShards` — it's still known, just not running.
18
18
  */
19
19
  import { shell } from '../shellRuntime.svelte';
20
- import { registerView, unregisterView } from './registry';
20
+ import { registerView, unregisterView, registerVerb as fwRegisterVerb, unregisterVerb as fwUnregisterVerb } from './registry';
21
21
  import { createDocumentHandle, getTenantId, getDocumentBackend } from '../documents';
22
22
  import { fetchEnvState, putEnvState } from '../env/client';
23
23
  import { isAdmin as checkIsAdmin } from '../auth/index';
@@ -75,7 +75,7 @@ export async function activateShard(id) {
75
75
  // and is now being required by an app). Idempotent — no error.
76
76
  return;
77
77
  }
78
- const entry = { shard, ctx: undefined, viewIds: new Set(), cleanupFns: [] };
78
+ const entry = { shard, ctx: undefined, viewIds: new Set(), verbNames: new Set(), cleanupFns: [] };
79
79
  // envState holds the reactive env data for this shard.
80
80
  // Must be declared with $state at variable declaration time (Svelte 5 rule).
81
81
  const envState = $state({
@@ -88,6 +88,11 @@ export async function activateShard(id) {
88
88
  registerView(viewId, factory);
89
89
  entry.viewIds.add(viewId);
90
90
  },
91
+ registerVerb: (verb) => {
92
+ const prefixed = id === 'shell' ? verb.name : `${id}:${verb.name}`;
93
+ fwRegisterVerb(prefixed, Object.assign(Object.assign({}, verb), { name: prefixed }));
94
+ entry.verbNames.add(prefixed);
95
+ },
91
96
  documents: (options) => {
92
97
  const handle = createDocumentHandle(getTenantId(), id, getDocumentBackend(), options);
93
98
  entry.cleanupFns.push(() => handle.dispose());
@@ -163,6 +168,8 @@ export function deactivateShard(id) {
163
168
  // Flush and dispose document handles before tearing down views.
164
169
  for (const fn of entry.cleanupFns)
165
170
  void fn();
171
+ for (const name of entry.verbNames)
172
+ fwUnregisterVerb(name);
166
173
  for (const viewId of entry.viewIds)
167
174
  unregisterView(viewId);
168
175
  active.delete(id);
@@ -2,3 +2,8 @@ import type { ViewFactory } from './types';
2
2
  export declare function registerView(viewId: string, factory: ViewFactory): void;
3
3
  export declare function getView(viewId: string): ViewFactory | undefined;
4
4
  export declare function unregisterView(viewId: string): void;
5
+ import type { Verb } from '../verbs/types';
6
+ export declare function registerVerb(name: string, verb: Verb): void;
7
+ export declare function getVerb(name: string): Verb | undefined;
8
+ export declare function unregisterVerb(name: string): void;
9
+ export declare function listVerbs(): Verb[];
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Contribution registry — phase 4 stub.
2
+ * Contribution registry — views and verbs.
3
3
  *
4
4
  * Tracks which ViewFactory answers a given viewId. In this phase the
5
5
  * registry is a flat module-level Map with no awareness of shard identity;
@@ -10,8 +10,8 @@
10
10
  * The shape of this registry is deliberately narrow so later phases can
11
11
  * expand it without breaking callers:
12
12
  * - Resolution by viewId is the only query slots need.
13
- * - Commands, toolbar items, menus, hotkeys get their own sibling maps
14
- * (one per contribution kind) when those kinds land.
13
+ * - Verbs are the second contribution kind (SH11). Toolbar items, menus,
14
+ * hotkeys get their own sibling maps when those kinds land.
15
15
  */
16
16
  const views = new Map();
17
17
  export function registerView(viewId, factory) {
@@ -26,3 +26,19 @@ export function getView(viewId) {
26
26
  export function unregisterView(viewId) {
27
27
  views.delete(viewId);
28
28
  }
29
+ const verbs = new Map();
30
+ export function registerVerb(name, verb) {
31
+ if (verbs.has(name)) {
32
+ throw new Error(`Verb "${name}" is already registered`);
33
+ }
34
+ verbs.set(name, verb);
35
+ }
36
+ export function getVerb(name) {
37
+ return verbs.get(name);
38
+ }
39
+ export function unregisterVerb(name) {
40
+ verbs.delete(name);
41
+ }
42
+ export function listVerbs() {
43
+ return Array.from(verbs.values()).sort((a, b) => a.name.localeCompare(b.name));
44
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { registerVerb, getVerb, unregisterVerb, listVerbs, } from './registry';
3
+ function makeStubVerb(name) {
4
+ return { name, summary: `stub ${name}`, run: async () => { } };
5
+ }
6
+ describe('verb registry', () => {
7
+ const registered = [];
8
+ beforeEach(() => {
9
+ for (const name of registered)
10
+ unregisterVerb(name);
11
+ registered.length = 0;
12
+ });
13
+ function trackVerb(name, verb) {
14
+ registerVerb(name, verb);
15
+ registered.push(name);
16
+ }
17
+ it('registers and retrieves a verb by name', () => {
18
+ const verb = makeStubVerb('foo');
19
+ trackVerb('foo', verb);
20
+ expect(getVerb('foo')).toBe(verb);
21
+ });
22
+ it('returns undefined for unknown verb', () => {
23
+ expect(getVerb('nope')).toBeUndefined();
24
+ });
25
+ it('throws on duplicate verb name', () => {
26
+ trackVerb('dup', makeStubVerb('dup'));
27
+ expect(() => trackVerb('dup', makeStubVerb('dup'))).toThrowError('Verb "dup" is already registered');
28
+ });
29
+ it('unregisters a verb', () => {
30
+ trackVerb('gone', makeStubVerb('gone'));
31
+ unregisterVerb('gone');
32
+ registered.pop();
33
+ expect(getVerb('gone')).toBeUndefined();
34
+ });
35
+ it('lists verbs sorted by name', () => {
36
+ trackVerb('zeta', makeStubVerb('zeta'));
37
+ trackVerb('alpha', makeStubVerb('alpha'));
38
+ trackVerb('mid', makeStubVerb('mid'));
39
+ const names = listVerbs().map((v) => v.name);
40
+ expect(names).toEqual(['alpha', 'mid', 'zeta']);
41
+ });
42
+ it('stores prefixed name inside verb object (mirrors activate auto-prefix)', () => {
43
+ // activate.svelte.ts does: { ...verb, name: prefixed }
44
+ const original = makeStubVerb('install');
45
+ const prefixed = Object.assign(Object.assign({}, original), { name: 'registry:install' });
46
+ trackVerb('registry:install', prefixed);
47
+ const found = getVerb('registry:install');
48
+ expect(found === null || found === void 0 ? void 0 : found.name).toBe('registry:install');
49
+ expect(found === null || found === void 0 ? void 0 : found.summary).toBe('stub install');
50
+ });
51
+ it('bulk unregister simulates deactivate cleanup', () => {
52
+ // activate.svelte.ts tracks verbNames and unregisters on deactivate
53
+ const names = ['registry:install', 'registry:search', 'registry:info'];
54
+ for (const name of names)
55
+ trackVerb(name, makeStubVerb(name));
56
+ expect(listVerbs()).toHaveLength(3);
57
+ for (const name of names)
58
+ unregisterVerb(name);
59
+ registered.length = 0; // already cleaned
60
+ expect(listVerbs()).toHaveLength(0);
61
+ });
62
+ });
@@ -2,6 +2,7 @@ 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
4
  import type { EnvState } from '../env/types';
5
+ import type { Verb } from '../verbs/types';
5
6
  /**
6
7
  * The object returned by `ViewFactory.mount`. The framework calls
7
8
  * `unmount()` when the slot goes away, and `onResize(w, h)` whenever the
@@ -77,16 +78,18 @@ export interface ViewDeclaration {
77
78
  icon?: string;
78
79
  }
79
80
  /**
80
- * Static description of a shard. Declared once and read by the framework
81
- * before `activate` runs, so the shell can enumerate a shard's capabilities
82
- * without executing any shard code.
81
+ * Static description of a shard as observed by the framework and consumers
82
+ * at runtime. `version` is always present here: for externally installed
83
+ * packages it is stamped by the installer from the registry entry, for
84
+ * in-tree shards it is set from `VERSION` (the auto-generated constant
85
+ * derived from `sh3-core`'s own `package.json`). See ADR-013.
83
86
  */
84
87
  export interface ShardManifest {
85
88
  /** Unique shard identifier. Used as the state namespace and view id prefix. */
86
89
  id: string;
87
90
  /** Human-readable display name. */
88
91
  label: string;
89
- /** Semver version string for the shard package. */
92
+ /** Semver version string. Always present at load time — see `SourceShardManifest` for what authors write. */
90
93
  version: string;
91
94
  /**
92
95
  * Static list of the view ids this shard provides. Every id listed here
@@ -111,6 +114,17 @@ export interface ShardManifest {
111
114
  */
112
115
  permissions?: string[];
113
116
  }
117
+ /**
118
+ * Source-declared shape of a shard manifest — what external package authors
119
+ * write in their own code. `version` is omitted because it would duplicate
120
+ * `package.json.version`; the framework injects it at load time. See ADR-013.
121
+ *
122
+ * Authors who want a source file type can use `SourceShard` (which carries
123
+ * `manifest: SourceShardManifest`). In-tree shards that import `VERSION`
124
+ * from `sh3-core`'s auto-generated `version.ts` can keep using the full
125
+ * `Shard` / `ShardManifest` types directly.
126
+ */
127
+ export type SourceShardManifest = Omit<ShardManifest, 'version'>;
114
128
  /**
115
129
  * Handed to `shard.activate`. The shard uses it to declare state and
116
130
  * register contributions. `state` is pre-bound to the shard's id so the
@@ -135,6 +149,14 @@ export interface ShardContext {
135
149
  * @param factory - The adapter that mounts the view into a container element.
136
150
  */
137
151
  registerView(viewId: string, factory: ViewFactory): void;
152
+ /**
153
+ * Register a verb that users can invoke from the shell terminal.
154
+ * The verb name is auto-prefixed with `shardId:` for non-shell shards.
155
+ * Automatically unregistered when the shard deactivates.
156
+ *
157
+ * @param verb - The verb definition (name, summary, run function).
158
+ */
159
+ registerVerb(verb: Verb): void;
138
160
  /** Obtain a file-oriented document handle scoped to this shard. */
139
161
  documents(options: DocumentHandleOptions): DocumentHandle;
140
162
  /**
@@ -205,3 +227,13 @@ export interface Shard {
205
227
  */
206
228
  resume?(ctx: ShardContext): void | Promise<void>;
207
229
  }
230
+ /**
231
+ * Source-level shape of a shard as written by external package authors.
232
+ * Carries a `SourceShardManifest` (no `version` field). The framework
233
+ * injects `version` from the registry entry's `PackageVersion.version`
234
+ * at install time, after which the object is observed as a full `Shard`.
235
+ * See ADR-013.
236
+ */
237
+ export interface SourceShard extends Omit<Shard, 'manifest'> {
238
+ manifest: SourceShardManifest;
239
+ }
@@ -5,7 +5,6 @@
5
5
  import InputLine from './InputLine.svelte';
6
6
  import { SessionClient } from './session-client.svelte';
7
7
  import { VerbRegistry, type ShellApi } from './registry';
8
- import { registerV1Verbs } from './verbs';
9
8
  import type { ServerMessage } from './protocol';
10
9
 
11
10
  interface Props {
@@ -18,13 +17,13 @@
18
17
  // wsUrl is a prop read at construction only. untrack prevents Svelte 5's
19
18
  // "referenced outside a closure" warning; the URL never changes at runtime.
20
19
  const session = untrack(() => new SessionClient(wsUrl));
21
- const registry = new VerbRegistry();
22
- registerV1Verbs(registry);
20
+ const resolver = new VerbRegistry();
23
21
 
24
22
  let locked = $state(false);
25
23
 
26
24
  async function dispatch(line: string): Promise<void> {
27
- const resolution = registry.resolve(line);
25
+ session.history.push(line);
26
+ const resolution = resolver.resolve(line);
28
27
  if (resolution.kind === 'local') {
29
28
  // Log locally-dispatched verbs for shared history
30
29
  session.send({ t: 'history-log', line });
@@ -71,14 +70,20 @@
71
70
  scrollback.push({ kind: 'text', stream: 'stderr', chunks: [e.data], ts: e.ts });
72
71
  break;
73
72
  case 'exit':
74
- scrollback.push({
75
- kind: 'status',
76
- text: e.signal
77
- ? `shell: process exited (${e.signal})`
78
- : `shell: process exited (${e.code ?? 0})`,
79
- level: e.code === 0 || e.code === null ? 'info' : 'error',
80
- ts: e.ts,
81
- });
73
+ // Match real-shell UX: stay silent on clean exit. Only surface
74
+ // a status line on non-zero exit codes or signal-terminated
75
+ // processes (SIGINT, spawn errors, etc.). `code === null` with
76
+ // no signal happens on clean close too — treat as success.
77
+ if (e.signal || (e.code !== null && e.code !== 0)) {
78
+ scrollback.push({
79
+ kind: 'status',
80
+ text: e.signal
81
+ ? `shell: process exited (${e.signal})`
82
+ : `shell: process exited (${e.code})`,
83
+ level: 'error',
84
+ ts: e.ts,
85
+ });
86
+ }
82
87
  locked = false;
83
88
  break;
84
89
  case 'status':
@@ -1,7 +1,8 @@
1
+ import { VERSION } from '../version';
1
2
  export const manifest = {
2
3
  id: 'shell',
3
4
  label: 'Shell',
4
- version: '0.1.0',
5
+ version: VERSION,
5
6
  views: [{ id: 'shell:terminal', label: 'Shell' }],
6
7
  // serverBundle intentionally omitted — this shard is a framework built-in
7
8
  // and is statically mounted at sh3-server boot. The existing contract in
@@ -1,68 +1,6 @@
1
- import type { Scrollback } from './scrollback.svelte';
2
- import type { SessionClient } from './session-client.svelte';
3
- export interface ShellApi {
4
- listApps(): Array<{
5
- id: string;
6
- label: string;
7
- }>;
8
- getActiveApp(): {
9
- id: string;
10
- label: string;
11
- } | null;
12
- launchApp(id: string): void;
13
- listShards(): Array<{
14
- id: string;
15
- label: string;
16
- version: string;
17
- }>;
18
- listViewsInCurrentLayout(): Array<{
19
- slotId: string;
20
- viewId: string;
21
- label: string;
22
- }>;
23
- openViewInCurrentLayout(viewId: string): {
24
- ok: boolean;
25
- error?: string;
26
- };
27
- closeSlot(slotId: string): {
28
- ok: boolean;
29
- error?: string;
30
- };
31
- listZones(shardId?: string): Array<{
32
- shardId: string;
33
- zones: string[];
34
- }>;
35
- readZone(shardId: string, zoneName: string): unknown;
36
- whoAmI(): {
37
- userId: string;
38
- admin: boolean;
39
- };
40
- }
41
- export interface VerbContext {
42
- shell: ShellApi;
43
- scrollback: Scrollback;
44
- session: SessionClient;
45
- cwd: string;
46
- /** Invoke another registered verb programmatically (used by rich-entry clicks). */
47
- dispatch(line: string): Promise<void>;
48
- }
49
- export interface Verb {
50
- name: string;
51
- summary: string;
52
- run(ctx: VerbContext, args: string[]): Promise<void>;
53
- }
54
- export type Resolution = {
55
- kind: 'local';
56
- verb: Verb;
57
- args: string[];
58
- line: string;
59
- } | {
60
- kind: 'forward';
61
- line: string;
62
- };
1
+ import type { Verb, VerbContext, Resolution, ShellApi } from '../verbs/types';
2
+ export type { Verb, VerbContext, Resolution, ShellApi };
63
3
  export declare class VerbRegistry {
64
- private readonly verbs;
65
- register(verb: Verb): void;
66
4
  list(): Verb[];
67
5
  get(name: string): Verb | undefined;
68
6
  resolve(line: string): Resolution;
@@ -1,26 +1,18 @@
1
1
  /*
2
- * Verb registry for shell-shard.
2
+ * Verb resolution for shell-shard.
3
3
  *
4
- * v1 is internal to shell-shard (not a contribution kind). Holds a map of
5
- * verb name Verb definition. Resolution:
6
- * 1. If first token matches a registered verb { kind: 'local', verb, args }
7
- * 2. If first token is '$' → { kind: 'forward', line: rest } (escape hatch)
8
- * 3. Otherwise → { kind: 'forward', line } (unknown, ship to server)
9
- *
10
- * Future: other shards register verbs via a new contribution kind.
4
+ * The VerbRegistry class owns resolution logic (escape hatch, token
5
+ * lookup, forward-to-server). Verb storage is the framework's job —
6
+ * see shards/registry.ts. This class reads from the framework map
7
+ * via getVerb() and listVerbs().
11
8
  */
9
+ import { getVerb, listVerbs } from '../shards/registry';
12
10
  export class VerbRegistry {
13
- constructor() {
14
- this.verbs = new Map();
15
- }
16
- register(verb) {
17
- this.verbs.set(verb.name, verb);
18
- }
19
11
  list() {
20
- return Array.from(this.verbs.values()).sort((a, b) => a.name.localeCompare(b.name));
12
+ return listVerbs();
21
13
  }
22
14
  get(name) {
23
- return this.verbs.get(name);
15
+ return getVerb(name);
24
16
  }
25
17
  resolve(line) {
26
18
  const trimmed = line.trim();
@@ -37,7 +29,7 @@ export class VerbRegistry {
37
29
  const space = trimmed.indexOf(' ');
38
30
  const head = space === -1 ? trimmed : trimmed.slice(0, space);
39
31
  const rest = space === -1 ? '' : trimmed.slice(space + 1);
40
- const verb = this.verbs.get(head);
32
+ const verb = getVerb(head);
41
33
  if (!verb)
42
34
  return { kind: 'forward', line };
43
35
  // Simple space-split for args — verbs can re-tokenize if they need quoting
@@ -15,6 +15,7 @@
15
15
  import { mount, unmount } from 'svelte';
16
16
  import { manifest } from './manifest';
17
17
  import Terminal from './Terminal.svelte';
18
+ import { registerV1Verbs } from './verbs';
18
19
  import { listRegisteredApps, getActiveApp } from '../apps/registry.svelte';
19
20
  import { launchApp } from '../apps/lifecycle';
20
21
  import { registeredShards } from '../shards/activate.svelte';
@@ -62,7 +63,7 @@ function makeShellApi(_ctx) {
62
63
  listViewsInCurrentLayout() {
63
64
  try {
64
65
  const { root } = inspectActiveLayout();
65
- return collectTabEntries(root).map((t) => {
66
+ return collectTabEntries(root.docked).map((t) => {
66
67
  var _a;
67
68
  return ({
68
69
  slotId: t.slotId,
@@ -114,6 +115,7 @@ export const shellShard = {
114
115
  // Non-admin: don't expose the view. Nothing to register.
115
116
  return;
116
117
  }
118
+ registerV1Verbs(ctx);
117
119
  const shell = makeShellApi(ctx);
118
120
  const factory = {
119
121
  mount(container, _context) {
@@ -128,6 +130,7 @@ export const shellShard = {
128
130
  unmount() {
129
131
  unmount(instance);
130
132
  },
133
+ closable: true,
131
134
  };
132
135
  },
133
136
  };
@@ -1,3 +1,3 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const appsVerb: Verb;
3
3
  export declare const appVerb: Verb;
@@ -1,2 +1,2 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const clearVerb: Verb;
@@ -1,2 +1,2 @@
1
- import type { Verb, VerbRegistry } from '../registry';
2
- export declare function makeHelpVerb(registry: VerbRegistry): Verb;
1
+ import type { Verb } from '../../verbs/types';
2
+ export declare function makeHelpVerb(): Verb;
@@ -1,10 +1,11 @@
1
+ import { listVerbs } from '../../shards/registry';
1
2
  import HelpTable from '../rich/HelpTable.svelte';
2
- export function makeHelpVerb(registry) {
3
+ export function makeHelpVerb() {
3
4
  return {
4
5
  name: 'help',
5
6
  summary: 'List verbs or show detail for one.',
6
7
  async run(ctx) {
7
- const rows = registry.list().map((v) => ({ name: v.name, summary: v.summary }));
8
+ const rows = listVerbs().map((v) => ({ name: v.name, summary: v.summary }));
8
9
  ctx.scrollback.push({
9
10
  kind: 'rich',
10
11
  component: HelpTable,
@@ -1,2 +1,2 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const historyVerb: Verb;
@@ -1,2 +1,2 @@
1
- import type { VerbRegistry } from '../registry';
2
- export declare function registerV1Verbs(registry: VerbRegistry): void;
1
+ import type { ShardContext } from '../../shards/types';
2
+ export declare function registerV1Verbs(ctx: ShardContext): void;
@@ -1,6 +1,6 @@
1
1
  /*
2
- * Bundle of v1 verbs for shell-shard. Call registerV1Verbs(registry) once
3
- * at activate time to populate the registry.
2
+ * Bundle of v1 verbs for shell-shard. Call registerV1Verbs(ctx) once
3
+ * during shell-shard activate() to populate the framework verb registry.
4
4
  */
5
5
  import { makeHelpVerb } from './help';
6
6
  import { clearVerb } from './clear';
@@ -10,20 +10,20 @@ import { shardsVerb } from './shards';
10
10
  import { viewsVerb, openVerb, closeVerb } from './views';
11
11
  import { zonesVerb, zoneVerb } from './zones';
12
12
  import { pwdVerb, cdVerb, envVerb, whoamiVerb } from './session';
13
- export function registerV1Verbs(registry) {
14
- registry.register(makeHelpVerb(registry));
15
- registry.register(clearVerb);
16
- registry.register(historyVerb);
17
- registry.register(appsVerb);
18
- registry.register(appVerb);
19
- registry.register(shardsVerb);
20
- registry.register(viewsVerb);
21
- registry.register(openVerb);
22
- registry.register(closeVerb);
23
- registry.register(zonesVerb);
24
- registry.register(zoneVerb);
25
- registry.register(pwdVerb);
26
- registry.register(cdVerb);
27
- registry.register(envVerb);
28
- registry.register(whoamiVerb);
13
+ export function registerV1Verbs(ctx) {
14
+ ctx.registerVerb(makeHelpVerb());
15
+ ctx.registerVerb(clearVerb);
16
+ ctx.registerVerb(historyVerb);
17
+ ctx.registerVerb(appsVerb);
18
+ ctx.registerVerb(appVerb);
19
+ ctx.registerVerb(shardsVerb);
20
+ ctx.registerVerb(viewsVerb);
21
+ ctx.registerVerb(openVerb);
22
+ ctx.registerVerb(closeVerb);
23
+ ctx.registerVerb(zonesVerb);
24
+ ctx.registerVerb(zoneVerb);
25
+ ctx.registerVerb(pwdVerb);
26
+ ctx.registerVerb(cdVerb);
27
+ ctx.registerVerb(envVerb);
28
+ ctx.registerVerb(whoamiVerb);
29
29
  }
@@ -1,4 +1,4 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const pwdVerb: Verb;
3
3
  export declare const cdVerb: Verb;
4
4
  export declare const envVerb: Verb;
@@ -1,2 +1,2 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const shardsVerb: Verb;
@@ -1,4 +1,4 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const viewsVerb: Verb;
3
3
  export declare const openVerb: Verb;
4
4
  export declare const closeVerb: Verb;
@@ -1,3 +1,3 @@
1
- import type { Verb } from '../registry';
1
+ import type { Verb } from '../../verbs/types';
2
2
  export declare const zonesVerb: Verb;
3
3
  export declare const zoneVerb: Verb;
@@ -3,6 +3,8 @@ import type { ZoneSchema } from './state/types';
3
3
  import { type ModalManager } from './overlays/modal';
4
4
  import { type PopupManager } from './overlays/popup';
5
5
  import { type ToastManager } from './overlays/toast';
6
+ import { type FloatManager } from './overlays/float';
7
+ import { type PresetManager } from './overlays/presets';
6
8
  /**
7
9
  * The process-wide shell singleton exposed to shards and the shell's own
8
10
  * internal code. Provides state zone creation and overlay managers.
@@ -22,6 +24,10 @@ export interface Shell {
22
24
  popup: PopupManager;
23
25
  /** Auto-dismissing notification toasts. */
24
26
  toast: ToastManager;
27
+ /** Detached floating panels on overlay layer 1. See overlays/float.ts. */
28
+ float: FloatManager;
29
+ /** Named layout presets per app. See overlays/presets.ts. */
30
+ presets: PresetManager;
25
31
  }
26
32
  /** The process-wide shell instance. Framework-internal code uses this directly; shards receive a scoped view via `ShardContext`. */
27
33
  export declare const shell: Shell;
@@ -18,10 +18,14 @@ import { createStateZones } from './state/zones.svelte';
18
18
  import { modalManager } from './overlays/modal';
19
19
  import { popupManager } from './overlays/popup';
20
20
  import { toastManager } from './overlays/toast';
21
+ import { floatManager } from './overlays/float';
22
+ import { presetManager } from './overlays/presets';
21
23
  /** The process-wide shell instance. Framework-internal code uses this directly; shards receive a scoped view via `ShardContext`. */
22
24
  export const shell = {
23
25
  state: createStateZones,
24
26
  modal: modalManager,
25
27
  popup: popupManager,
26
28
  toast: toastManager,
29
+ float: floatManager,
30
+ presets: presetManager,
27
31
  };