sh3-core 0.21.2 → 0.22.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 (96) hide show
  1. package/dist/__test__/fixtures.js +1 -1
  2. package/dist/__test__/reset.js +1 -1
  3. package/dist/__test__/smoke.test.js +2 -2
  4. package/dist/actions/contextMenuModel.test.js +6 -3
  5. package/dist/actions/ctx-actions.svelte.test.js +9 -9
  6. package/dist/actions/dispatcher-v3.test.js +8 -0
  7. package/dist/actions/dispatcher.svelte.d.ts +1 -2
  8. package/dist/actions/dispatcher.svelte.js +6 -7
  9. package/dist/actions/dispatcher.test.js +9 -12
  10. package/dist/actions/listActionsFromEntries.test.js +1 -2
  11. package/dist/actions/listActive.test.js +2 -3
  12. package/dist/actions/menuBarModel.test.js +1 -7
  13. package/dist/actions/paletteModel.test.js +1 -3
  14. package/dist/actions/scope-helpers.test.js +4 -4
  15. package/dist/actions/shardContext.test.js +2 -2
  16. package/dist/actions/state.svelte.d.ts +12 -2
  17. package/dist/actions/state.svelte.js +15 -12
  18. package/dist/actions/state.test.js +4 -4
  19. package/dist/api.d.ts +3 -3
  20. package/dist/api.js +1 -1
  21. package/dist/app/admin/adminShard.svelte.js +1 -1
  22. package/dist/app/store/storeShard.svelte.js +10 -5
  23. package/dist/app-appearance/appearanceShard.svelte.js +1 -5
  24. package/dist/apps/lifecycle.js +65 -33
  25. package/dist/apps/lifecycle.test.js +198 -10
  26. package/dist/artifact.d.ts +2 -0
  27. package/dist/build.js +1 -1
  28. package/dist/conflicts/adapter-documents.js +1 -2
  29. package/dist/createShell.js +1 -1
  30. package/dist/documents/handle.d.ts +9 -4
  31. package/dist/documents/handle.js +69 -45
  32. package/dist/documents/handle.test.js +99 -27
  33. package/dist/documents/index.d.ts +1 -1
  34. package/dist/documents/types.d.ts +16 -20
  35. package/dist/host.d.ts +1 -1
  36. package/dist/host.js +9 -56
  37. package/dist/host.svelte.test.js +31 -63
  38. package/dist/layout/LayoutRenderer.svelte +1 -1
  39. package/dist/layout/SlotContainer.svelte +1 -0
  40. package/dist/layout/inspection.js +19 -14
  41. package/dist/layout/inspection.svelte.test.js +136 -1
  42. package/dist/layout/slotHostPool.svelte.d.ts +2 -1
  43. package/dist/layout/slotHostPool.svelte.js +6 -3
  44. package/dist/layout/slotHostPool.test.js +17 -0
  45. package/dist/layout/store.projectScope.test.js +76 -0
  46. package/dist/layout/store.svelte.d.ts +6 -0
  47. package/dist/layout/store.svelte.js +43 -13
  48. package/dist/layout/tree-walk.d.ts +8 -1
  49. package/dist/layout/tree-walk.js +11 -1
  50. package/dist/layout/tree-walk.test.js +53 -1
  51. package/dist/layout/types.d.ts +27 -0
  52. package/dist/layout/types.test.js +28 -0
  53. package/dist/layouts-shard/LayoutsSection.svelte +1 -1
  54. package/dist/layouts-shard/layoutsShard.svelte.js +2 -5
  55. package/dist/layouts-shard/layoutsShard.svelte.test.js +2 -2
  56. package/dist/overlays/FloatFrame.svelte +4 -1
  57. package/dist/overlays/float.d.ts +7 -1
  58. package/dist/overlays/float.js +4 -0
  59. package/dist/projects-shard/ProjectsSection.svelte +1 -5
  60. package/dist/projects-shard/projectsShard.svelte.js +1 -5
  61. package/dist/registry/installer.js +1 -1
  62. package/dist/registry/loader.d.ts +1 -1
  63. package/dist/registry/loader.js +3 -3
  64. package/dist/registry/permission-descriptions.test.js +2 -2
  65. package/dist/registry/register.js +1 -1
  66. package/dist/registry/register.test.js +1 -1
  67. package/dist/runtime/runVerb-shell.test.js +1 -1
  68. package/dist/runtime/runVerb.js +2 -2
  69. package/dist/runtime/runVerb.test.js +9 -9
  70. package/dist/sh3Api/headless.js +1 -1
  71. package/dist/sh3core-shard/sh3coreShard.svelte.js +1 -6
  72. package/dist/shards/ctx-fetch.test.js +9 -9
  73. package/dist/shards/lifecycle.svelte.d.ts +108 -0
  74. package/dist/shards/lifecycle.svelte.js +551 -0
  75. package/dist/shards/lifecycle.test.js +139 -0
  76. package/dist/shards/types.d.ts +56 -22
  77. package/dist/shell-shard/shellShard.svelte.js +11 -5
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/package.json +1 -1
  81. package/dist/shards/activate-browse.test.js +0 -120
  82. package/dist/shards/activate-contributions.test.js +0 -141
  83. package/dist/shards/activate-error-isolation.test.js +0 -98
  84. package/dist/shards/activate-fields.svelte.test.d.ts +0 -1
  85. package/dist/shards/activate-fields.svelte.test.js +0 -121
  86. package/dist/shards/activate-on-key-revoked.test.d.ts +0 -1
  87. package/dist/shards/activate-on-key-revoked.test.js +0 -60
  88. package/dist/shards/activate-runtime.test.d.ts +0 -1
  89. package/dist/shards/activate-runtime.test.js +0 -299
  90. package/dist/shards/activate-scopeid.test.d.ts +0 -1
  91. package/dist/shards/activate-scopeid.test.js +0 -21
  92. package/dist/shards/activate.svelte.d.ts +0 -102
  93. package/dist/shards/activate.svelte.js +0 -403
  94. /package/dist/{shards/activate-browse.test.d.ts → actions/dispatcher-v3.test.d.ts} +0 -0
  95. /package/dist/{shards/activate-contributions.test.d.ts → layout/store.projectScope.test.d.ts} +0 -0
  96. /package/dist/shards/{activate-error-isolation.test.d.ts → lifecycle.test.d.ts} +0 -0
@@ -20,12 +20,13 @@ export function collectSlotRefs(tree) {
20
20
  viewId: node.viewId,
21
21
  label: node.viewId || node.slotId,
22
22
  meta: node.meta,
23
+ props: node.props,
23
24
  });
24
25
  return;
25
26
  }
26
27
  if (node.type === 'tabs') {
27
28
  for (const t of node.tabs) {
28
- out.push({ slotId: t.slotId, viewId: t.viewId, label: t.label, meta: t.meta });
29
+ out.push({ slotId: t.slotId, viewId: t.viewId, label: t.label, meta: t.meta, props: t.props });
29
30
  }
30
31
  return;
31
32
  }
@@ -49,3 +50,12 @@ export function collectTreeSlotRefs(tree) {
49
50
  }
50
51
  return out;
51
52
  }
53
+ /**
54
+ * Collect every slot that has a non-null viewId, for use by shard layout
55
+ * hooks (`onLayoutWillRestore`, `onLayoutRestored`). Excludes empty slots.
56
+ */
57
+ export function collectRestoredSlots(tree) {
58
+ return collectTreeSlotRefs(tree)
59
+ .filter((r) => r.viewId !== null)
60
+ .map(({ slotId, viewId, props }) => ({ slotId, viewId, props }));
61
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { collectSlotRefs, collectTreeSlotRefs } from './tree-walk';
2
+ import { collectSlotRefs, collectTreeSlotRefs, collectRestoredSlots } from './tree-walk';
3
3
  const slot = (slotId, viewId) => ({
4
4
  type: 'slot',
5
5
  slotId,
@@ -12,6 +12,58 @@ describe('collectSlotRefs (single LayoutNode — existing behavior)', () => {
12
12
  ]);
13
13
  });
14
14
  });
15
+ describe('collectSlotRefs — props', () => {
16
+ it('includes props from a slot node', () => {
17
+ const tree = {
18
+ type: 'slot',
19
+ slotId: 's1',
20
+ viewId: 'v:a',
21
+ props: { filePath: '/a.guml' },
22
+ };
23
+ const refs = collectSlotRefs(tree);
24
+ expect(refs[0].props).toEqual({ filePath: '/a.guml' });
25
+ });
26
+ it('includes props from a tab entry', () => {
27
+ const tree = {
28
+ type: 'tabs',
29
+ activeTab: 0,
30
+ tabs: [{ slotId: 's2', viewId: 'v:b', label: 'B', props: { x: 42 } }],
31
+ };
32
+ const refs = collectSlotRefs(tree);
33
+ expect(refs[0].props).toEqual({ x: 42 });
34
+ });
35
+ it('props is undefined when not set', () => {
36
+ const tree = { type: 'slot', slotId: 's3', viewId: 'v:c' };
37
+ const refs = collectSlotRefs(tree);
38
+ expect(refs[0].props).toBeUndefined();
39
+ });
40
+ });
41
+ describe('collectRestoredSlots', () => {
42
+ it('collects slots from docked tree and floats, excluding nulls', () => {
43
+ const tree = {
44
+ docked: { type: 'slot', slotId: 'd1', viewId: 'v:d', props: { a: 1 } },
45
+ floats: [
46
+ {
47
+ id: 'f1',
48
+ content: { type: 'slot', slotId: 'f-s1', viewId: 'v:e' },
49
+ position: { x: 0, y: 0 },
50
+ size: { w: 100, h: 100 },
51
+ },
52
+ ],
53
+ };
54
+ const slots = collectRestoredSlots(tree);
55
+ expect(slots).toHaveLength(2);
56
+ expect(slots[0]).toEqual({ slotId: 'd1', viewId: 'v:d', props: { a: 1 } });
57
+ expect(slots[1]).toEqual({ slotId: 'f-s1', viewId: 'v:e', props: undefined });
58
+ });
59
+ it('excludes slots with null viewId', () => {
60
+ const tree = {
61
+ docked: { type: 'slot', slotId: 'e1', viewId: null },
62
+ floats: [],
63
+ };
64
+ expect(collectRestoredSlots(tree)).toHaveLength(0);
65
+ });
66
+ });
15
67
  describe('collectTreeSlotRefs (LayoutTree — new)', () => {
16
68
  it('returns docked slots followed by float content slots in order', () => {
17
69
  const tree = {
@@ -1,3 +1,17 @@
1
+ /** JSON-safe value — safe for serialization into the layout tree. */
2
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ /**
6
+ * A slot as seen by shard layout hooks (`onLayoutWillRestore`,
7
+ * `onLayoutRestored`). Carries the slot's persisted props so shards
8
+ * can re-run ceremony without querying the layout tree directly.
9
+ */
10
+ export interface RestoredSlot {
11
+ slotId: string;
12
+ viewId: string;
13
+ props?: Record<string, JsonValue>;
14
+ }
1
15
  /** Axis along which a split node divides its children. */
2
16
  export type SplitDirection = 'horizontal' | 'vertical';
3
17
  /**
@@ -62,6 +76,13 @@ export interface TabEntry {
62
76
  * Ephemeral — not serialized with the layout tree.
63
77
  */
64
78
  meta?: Record<string, unknown>;
79
+ /**
80
+ * Persistent view-level parameters. Serialized with the layout tree and
81
+ * passed to `ViewFactory.mount` on every mount, including layout restore.
82
+ * Values must be JSON-serializable. Use for data the view needs to
83
+ * initialize itself (e.g. `{ filePath: '/foo.guml' }`).
84
+ */
85
+ props?: Record<string, JsonValue>;
65
86
  }
66
87
  /**
67
88
  * A layout node that groups one or more slots as tabs, showing one at a time.
@@ -124,6 +145,12 @@ export interface SlotNode {
124
145
  * content is a single slot rather than a TabsNode).
125
146
  */
126
147
  meta?: Record<string, unknown>;
148
+ /**
149
+ * Persistent view-level parameters. Serialized with the layout tree and
150
+ * passed to `ViewFactory.mount` on every mount, including layout restore.
151
+ * Values must be JSON-serializable.
152
+ */
153
+ props?: Record<string, JsonValue>;
127
154
  }
128
155
  /**
129
156
  * Union of all layout node kinds. The recursive tree is composed entirely of
@@ -24,3 +24,31 @@ describe('LAYOUT_SCHEMA_VERSION', () => {
24
24
  expect(LAYOUT_SCHEMA_VERSION).toBe(7);
25
25
  });
26
26
  });
27
+ describe('SlotNode props', () => {
28
+ it('accepts optional props', () => {
29
+ var _a;
30
+ const node = {
31
+ type: 'slot',
32
+ slotId: 'slot-1',
33
+ viewId: 'my:view',
34
+ props: { filePath: '/foo.guml', count: 3, flag: true, nul: null },
35
+ };
36
+ expect((_a = node.props) === null || _a === void 0 ? void 0 : _a.filePath).toBe('/foo.guml');
37
+ });
38
+ it('omitting props is valid', () => {
39
+ const node = { type: 'slot', slotId: 's', viewId: null };
40
+ expect(node.props).toBeUndefined();
41
+ });
42
+ });
43
+ describe('TabEntry props', () => {
44
+ it('accepts optional props', () => {
45
+ var _a;
46
+ const entry = {
47
+ slotId: 'slot-1',
48
+ viewId: 'my:view',
49
+ label: 'My View',
50
+ props: { x: 1 },
51
+ };
52
+ expect((_a = entry.props) === null || _a === void 0 ? void 0 : _a.x).toBe(1);
53
+ });
54
+ });
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { getLayouts } from './layoutsState.svelte';
9
9
  import { restoreToFloat } from './layoutsApi';
10
- import { listStandaloneViews } from '../shards/activate.svelte';
10
+ import { listStandaloneViews } from '../shards/lifecycle.svelte';
11
11
  import { toastManager } from '../overlays/toast';
12
12
  import { sh3 } from '../sh3Runtime.svelte';
13
13
  import { makeSelectionApi } from '../actions/selection.svelte';
@@ -8,7 +8,7 @@
8
8
  * submenu and the home-page card grid.
9
9
  */
10
10
  import { VERSION } from '../version';
11
- import { listStandaloneViews } from '../shards/activate.svelte';
11
+ import { listStandaloneViews } from '../shards/lifecycle.svelte';
12
12
  import { getSelection } from '../actions/selection.svelte';
13
13
  import { modalManager } from '../overlays/modal';
14
14
  import { toastManager } from '../overlays/toast';
@@ -137,7 +137,7 @@ export const layoutsShard = {
137
137
  version: VERSION,
138
138
  views: [],
139
139
  },
140
- activate(ctx) {
140
+ register(ctx) {
141
141
  const zone = ctx.state({
142
142
  user: { layouts: [] },
143
143
  });
@@ -222,9 +222,6 @@ export const layoutsShard = {
222
222
  });
223
223
  });
224
224
  },
225
- autostart() {
226
- /* self-start so the action is available before any app launches. */
227
- },
228
225
  deactivate() {
229
226
  __unbindZone();
230
227
  },
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
- import { registerShard, activateShard } from '../shards/activate.svelte';
2
+ import { registerShard, activateShard } from '../shards/lifecycle.svelte';
3
3
  import { addAutostartShard } from '../actions/state.svelte';
4
4
  import { floatManager } from '../overlays/float';
5
5
  import { layoutsShard } from './layoutsShard.svelte';
@@ -15,7 +15,7 @@ const stubShard = {
15
15
  version: '0.0.0',
16
16
  views: [{ id: 'shell:terminal', label: 'Sh3', standalone: true }],
17
17
  },
18
- activate(ctx) {
18
+ register(ctx) {
19
19
  ctx.registerView('shell:terminal', {
20
20
  mount: () => ({ unmount() { } }),
21
21
  });
@@ -35,6 +35,7 @@
35
35
  import { makeSelectionApi } from '../actions/selection.svelte';
36
36
  import { spawnSatellite } from '../sh3Api/window';
37
37
  import { walkShardsForContent } from '../satellite/walkShards';
38
+ import { getActiveScopeId, getPersonalScopeId } from '../documents/config';
38
39
  import { logGesture } from '../gestures';
39
40
 
40
41
  const isTauri =
@@ -256,13 +257,15 @@
256
257
  async function onPopOut(e: MouseEvent): Promise<void> {
257
258
  e.stopPropagation();
258
259
  try {
260
+ const scopeId = getActiveScopeId();
261
+ const personalId = getPersonalScopeId();
259
262
  await spawnSatellite({
260
263
  kind: 'float',
261
264
  content: entry.content,
262
265
  title: entry.title,
263
266
  size: { w: entry.size.w, h: entry.size.h },
264
267
  activateShards: walkShardsForContent(entry.content),
265
- projectId: sh3.getActiveScope().isProject ? sh3.getActiveScope().id : undefined,
268
+ projectId: scopeId !== personalId ? scopeId : undefined,
266
269
  });
267
270
  floatManager.close(entry.id);
268
271
  } catch (err) {
@@ -1,4 +1,4 @@
1
- import type { LayoutNode, FloatEntry } from '../layout/types';
1
+ import type { LayoutNode, FloatEntry, JsonValue } from '../layout/types';
2
2
  import type { Size } from '../layout/floats';
3
3
  export interface FloatOptions {
4
4
  title?: string;
@@ -9,6 +9,12 @@ export interface FloatOptions {
9
9
  size?: Size;
10
10
  /** Instance data threaded to the view factory via `MountContext.meta`. */
11
11
  meta?: Record<string, unknown>;
12
+ /**
13
+ * Serializable view props threaded to the factory via `MountContext.props`
14
+ * and persisted with the float entry. Survives `popoutView` → `dockFloat`
15
+ * round-trip. JSON-safe values only.
16
+ */
17
+ props?: Record<string, JsonValue>;
12
18
  /**
13
19
  * When true, the float dismisses on any pointerdown outside its frame,
14
20
  * is not dockable, and renders without a header when `title` is unset.
@@ -150,6 +150,8 @@ function openFloat(viewId, options = {}) {
150
150
  };
151
151
  if (options.meta)
152
152
  slot.meta = options.meta;
153
+ if (options.props)
154
+ slot.props = options.props;
153
155
  content = slot;
154
156
  }
155
157
  else {
@@ -157,6 +159,8 @@ function openFloat(viewId, options = {}) {
157
159
  const tab = { slotId, viewId, label };
158
160
  if (options.meta)
159
161
  tab.meta = options.meta;
162
+ if (options.props)
163
+ tab.props = options.props;
160
164
  content = {
161
165
  type: 'tabs',
162
166
  tabs: [tab],
@@ -90,11 +90,7 @@
90
90
  border-color: var(--sh3-accent);
91
91
  transform: translateY(-1px);
92
92
  }
93
- .project-card.active {
94
- border-color: var(--sh3-accent);
95
- box-shadow: 0 0 0 2px color-mix(in srgb, var(--sh3-accent) 40%, transparent);
96
- }
97
- .project-name { font-weight: 600; font-size: 13px; }
93
+ .project-name { font-weight: 600; font-size: 13px; }
98
94
  .project-meta { font-size: 11px; color: var(--sh3-fg-muted); }
99
95
  .leave-project {
100
96
  display: flex;
@@ -56,7 +56,7 @@ export const projectsShard = {
56
56
  version: VERSION,
57
57
  views: [{ id: PROJECTS_MANAGE_VIEW, label: 'Project Manager' }],
58
58
  },
59
- activate(ctx) {
59
+ register(ctx) {
60
60
  void refreshProjects();
61
61
  if (typeof document !== 'undefined') {
62
62
  document.addEventListener('visibilitychange', () => {
@@ -141,8 +141,4 @@ export const projectsShard = {
141
141
  });
142
142
  });
143
143
  },
144
- autostart() {
145
- /* register on the self-starting path so the project list is available
146
- on the home screen without an app launch. */
147
- },
148
144
  };
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { loadBundleModule } from './loader';
18
18
  import { savePackage, loadBundle, listInstalled, removePackage } from './storage';
19
- import { deactivateShard } from '../shards/activate.svelte';
19
+ import { deactivateShard } from '../shards/lifecycle.svelte';
20
20
  import { unregisterApp } from '../apps/lifecycle';
21
21
  import { registerLoadedBundle } from './register';
22
22
  import { extractBundlePermissions } from './permission-descriptions';
@@ -37,7 +37,7 @@ export declare function loadBundleModule(bytes: ArrayBuffer): Promise<LoadedBund
37
37
  /**
38
38
  * Type guard: returns true if the loaded module is a shard.
39
39
  *
40
- * A shard has an `activate` function and `manifest.views` array. An app
40
+ * A shard has a `register` function and `manifest.views` array. An app
41
41
  * has neither — it has `initialLayout` and `manifest.requiredShards`.
42
42
  */
43
43
  export declare function isShard(mod: Shard | App): mod is Shard;
@@ -125,12 +125,12 @@ export async function loadBundleModule(bytes) {
125
125
  /**
126
126
  * Type guard: returns true if the loaded module is a shard.
127
127
  *
128
- * A shard has an `activate` function and `manifest.views` array. An app
128
+ * A shard has a `register` function and `manifest.views` array. An app
129
129
  * has neither — it has `initialLayout` and `manifest.requiredShards`.
130
130
  */
131
131
  export function isShard(mod) {
132
- return ('activate' in mod &&
133
- typeof mod.activate === 'function' &&
132
+ return ('register' in mod &&
133
+ typeof mod.register === 'function' &&
134
134
  Array.isArray(mod.manifest.views));
135
135
  }
136
136
  /**
@@ -40,7 +40,7 @@ describe('describePermission — documents:read / documents:write', () => {
40
40
  function shardWithPerms(id, perms) {
41
41
  return {
42
42
  manifest: { id, label: id, version: '0.0.0', views: [], permissions: perms },
43
- activate: () => { },
43
+ register: () => { },
44
44
  };
45
45
  }
46
46
  function appWithPerms(id, perms) {
@@ -54,7 +54,7 @@ function appWithPerms(id, perms) {
54
54
  layoutVersion: 1,
55
55
  permissions: perms,
56
56
  },
57
- activate: () => { },
57
+ register: () => { },
58
58
  };
59
59
  }
60
60
  describe('extractBundlePermissions', () => {
@@ -8,7 +8,7 @@
8
8
  * manifests; the authoritative value comes from the persisted/server
9
9
  * metadata and must be stamped here before any consumer reads the manifest.
10
10
  */
11
- import { registerShard } from '../shards/activate.svelte';
11
+ import { registerShard } from '../shards/lifecycle.svelte';
12
12
  import { registerApp } from '../apps/registry.svelte';
13
13
  export function registerLoadedBundle(loaded, meta) {
14
14
  for (const shard of loaded.shards) {
@@ -3,7 +3,7 @@ import { resetFramework } from '../__test__/reset';
3
3
  import { makeApp, makeAppManifest, makeShard, makeShardManifest } from '../__test__/fixtures';
4
4
  import { registerLoadedBundle } from './register';
5
5
  import { registeredApps } from '../apps/registry.svelte';
6
- import { registeredShards } from '../shards/activate.svelte';
6
+ import { registeredShards } from '../shards/lifecycle.svelte';
7
7
  describe('registerLoadedBundle', () => {
8
8
  beforeEach(resetFramework);
9
9
  it('stamps meta.version onto every shard manifest before registering', () => {
@@ -12,7 +12,7 @@
12
12
  import { describe, it, expect, beforeEach } from 'vitest';
13
13
  import { MemoryDocumentBackend } from '../documents/backends';
14
14
  import { __setDocumentBackend, __setActiveScope } from '../documents/config';
15
- import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
15
+ import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/lifecycle.svelte';
16
16
  import { __resetViewRegistryForTest } from '../shards/registry';
17
17
  import { __resetActionsRegistryForTest } from '../actions/registry';
18
18
  import { runVerbProgrammatic } from './runVerb';
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * runVerbProgrammatic — programmatic verb dispatch with synthesized VerbContext.
3
3
  *
4
- * Used by `ctx.runVerb(...)` (see shards/activate.svelte.ts). Builds a
4
+ * Used by `ctx.runVerb(...)` (see shards/lifecycle.svelte.ts). Builds a
5
5
  * sink scrollback that captures entries into an array, a headless Sh3Api
6
6
  * (no terminal-bound state), a stub SessionClient, and a real TenantFsClient.
7
7
  * Verbs that opt in via `programmatic: true` run against this synthesized
@@ -15,7 +15,7 @@
15
15
  * Resolution: prefixed names (`'sh3-store:install'`) look up directly;
16
16
  * unprefixed sh3 names (`'apps'`) resolve against shardId 'sh3'.
17
17
  */
18
- import { activeShards } from '../shards/activate.svelte';
18
+ import { activeShards } from '../shards/lifecycle.svelte';
19
19
  import { getVerb, listVerbsWithShard } from '../shards/registry';
20
20
  import { makeSh3Api } from '../sh3Api/headless';
21
21
  import { Scrollback } from '../shell-shard/scrollback.svelte';
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
2
  import { MemoryDocumentBackend } from '../documents/backends';
3
3
  import { __setDocumentBackend, __setActiveScope } from '../documents/config';
4
- import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/activate.svelte';
4
+ import { registerShard, activateShard, __resetShardRegistryForTest, } from '../shards/lifecycle.svelte';
5
5
  import { __resetViewRegistryForTest } from '../shards/registry';
6
6
  import { runVerbProgrammatic } from './runVerb';
7
7
  function makeVerb(name, programmatic, body = async () => undefined) {
@@ -27,7 +27,7 @@ describe('runVerbProgrammatic', () => {
27
27
  it('rejects on unknown verb', async () => {
28
28
  registerShard({
29
29
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
30
- activate(ctx) {
30
+ register(ctx) {
31
31
  ctx.registerVerb(makeVerb('echo', true));
32
32
  },
33
33
  });
@@ -37,7 +37,7 @@ describe('runVerbProgrammatic', () => {
37
37
  it('rejects when verb is not programmatic', async () => {
38
38
  registerShard({
39
39
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
40
- activate(ctx) {
40
+ register(ctx) {
41
41
  ctx.registerVerb(makeVerb('plain', false));
42
42
  },
43
43
  });
@@ -47,7 +47,7 @@ describe('runVerbProgrammatic', () => {
47
47
  it('invokes a programmatic verb and resolves with { result, scrollback }', async () => {
48
48
  registerShard({
49
49
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
50
- activate(ctx) {
50
+ register(ctx) {
51
51
  ctx.registerVerb(makeVerb('echo', true, async (vctx, args) => {
52
52
  vctx.scrollback.push({
53
53
  kind: 'status',
@@ -71,7 +71,7 @@ describe('runVerbProgrammatic', () => {
71
71
  let observed = undefined;
72
72
  registerShard({
73
73
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
74
- activate(ctx) {
74
+ register(ctx) {
75
75
  ctx.registerVerb(makeVerb('capture', true, async (vctx) => {
76
76
  observed = vctx.structuredArgs;
77
77
  }));
@@ -85,7 +85,7 @@ describe('runVerbProgrammatic', () => {
85
85
  let received;
86
86
  registerShard({
87
87
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
88
- activate(ctx) {
88
+ register(ctx) {
89
89
  ctx.registerVerb(makeVerb('peek', true, async (vctx) => {
90
90
  received = vctx.signal;
91
91
  }));
@@ -99,7 +99,7 @@ describe('runVerbProgrammatic', () => {
99
99
  it('inner dispatch re-enters runVerb and merges scrollback into the outer capture', async () => {
100
100
  registerShard({
101
101
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
102
- activate(ctx) {
102
+ register(ctx) {
103
103
  ctx.registerVerb(makeVerb('inner', true, async (vctx) => {
104
104
  vctx.scrollback.push({ kind: 'status', text: 'inner-fired', level: 'info', ts: 0 });
105
105
  }));
@@ -120,7 +120,7 @@ describe('runVerbProgrammatic', () => {
120
120
  it('propagates an error thrown by the verb', async () => {
121
121
  registerShard({
122
122
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
123
- activate(ctx) {
123
+ register(ctx) {
124
124
  ctx.registerVerb(makeVerb('boom', true, async () => {
125
125
  throw new Error('kaboom');
126
126
  }));
@@ -133,7 +133,7 @@ describe('runVerbProgrammatic', () => {
133
133
  let observed = undefined;
134
134
  registerShard({
135
135
  manifest: { id: 'tester', label: 'T', version: '0.0.0', views: [] },
136
- activate(ctx) {
136
+ register(ctx) {
137
137
  ctx.registerVerb(makeVerb('peek-sh3', true, async (vctx) => {
138
138
  observed = vctx.sh3.dispatchToTerminal('foo');
139
139
  }));
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { listRegisteredApps, getActiveApp } from '../apps/registry.svelte';
17
17
  import { launchApp } from '../apps/lifecycle';
18
- import { registeredShards, listStandaloneViews } from '../shards/activate.svelte';
18
+ import { registeredShards, listStandaloneViews } from '../shards/lifecycle.svelte';
19
19
  import { inspectActiveLayout, focusView, closeTab, popoutView, dockFloat, dockIntoActiveLayout, locateSlot as locateSlotInActiveLayout, } from '../layout/inspection';
20
20
  import { floatManager } from '../overlays/float';
21
21
  import { getUser, isAdmin } from '../auth/index';
@@ -65,7 +65,7 @@ export const sh3coreShard = {
65
65
  { id: 'sh3:keys-and-peers', label: 'Keys & Peers' },
66
66
  ],
67
67
  },
68
- activate(ctx) {
68
+ register(ctx) {
69
69
  const zones = ctx.state({
70
70
  user: {
71
71
  bindings: {},
@@ -165,9 +165,4 @@ export const sh3coreShard = {
165
165
  });
166
166
  });
167
167
  },
168
- autostart() {
169
- // Intentionally empty. Defining this field is what puts the sh3core
170
- // pseudo-shard on the self-starting path at boot (see bootstrap),
171
- // so `sh3core:home` is available before any app launches.
172
- },
173
168
  };
@@ -2,7 +2,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
2
  import { MemoryDocumentBackend } from '../documents/backends';
3
3
  import { __setDocumentBackend, __setActiveScope } from '../documents/config';
4
4
  import { __setEnvServerUrl } from '../env/index';
5
- import { registerShard, activateShard, __resetShardRegistryForTest, } from './activate.svelte';
5
+ import { registerShard, activateShard, __resetShardRegistryForTest, } from './lifecycle.svelte';
6
6
  import { __resetViewRegistryForTest } from './registry';
7
7
  describe('ctx.fetch', () => {
8
8
  let originalFetch;
@@ -27,7 +27,7 @@ describe('ctx.fetch', () => {
27
27
  let captured = null;
28
28
  registerShard({
29
29
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
30
- activate(ctx) { captured = ctx; },
30
+ register(ctx) { captured = ctx; },
31
31
  });
32
32
  await activateShard('test');
33
33
  await captured.fetch('/api/foo');
@@ -42,7 +42,7 @@ describe('ctx.fetch', () => {
42
42
  let captured = null;
43
43
  registerShard({
44
44
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
45
- activate(ctx) { captured = ctx; },
45
+ register(ctx) { captured = ctx; },
46
46
  });
47
47
  await activateShard('test');
48
48
  await captured.fetch('https://other.example.com/api/bar');
@@ -57,7 +57,7 @@ describe('ctx.fetch', () => {
57
57
  let captured = null;
58
58
  registerShard({
59
59
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
60
- activate(ctx) { captured = ctx; },
60
+ register(ctx) { captured = ctx; },
61
61
  });
62
62
  await activateShard('test');
63
63
  await captured.fetch('api/baz');
@@ -79,7 +79,7 @@ describe('ctx.serverUrl', () => {
79
79
  let captured = null;
80
80
  registerShard({
81
81
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
82
- activate(ctx) { captured = ctx; },
82
+ register(ctx) { captured = ctx; },
83
83
  });
84
84
  await activateShard('test');
85
85
  expect(captured.serverUrl).toBe('https://example.com');
@@ -89,7 +89,7 @@ describe('ctx.serverUrl', () => {
89
89
  let captured = null;
90
90
  registerShard({
91
91
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
92
- activate(ctx) { captured = ctx; },
92
+ register(ctx) { captured = ctx; },
93
93
  });
94
94
  await activateShard('test');
95
95
  expect(captured.serverUrl).toBe('');
@@ -110,7 +110,7 @@ describe('ctx.resolveUrl', () => {
110
110
  let captured = null;
111
111
  registerShard({
112
112
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
113
- activate(ctx) { captured = ctx; },
113
+ register(ctx) { captured = ctx; },
114
114
  });
115
115
  await activateShard('test');
116
116
  expect(captured.resolveUrl('/api/foo')).toBe('https://example.com/api/foo');
@@ -119,7 +119,7 @@ describe('ctx.resolveUrl', () => {
119
119
  let captured = null;
120
120
  registerShard({
121
121
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
122
- activate(ctx) { captured = ctx; },
122
+ register(ctx) { captured = ctx; },
123
123
  });
124
124
  await activateShard('test');
125
125
  expect(captured.resolveUrl('https://other.example.com/ws')).toBe('https://other.example.com/ws');
@@ -128,7 +128,7 @@ describe('ctx.resolveUrl', () => {
128
128
  let captured = null;
129
129
  registerShard({
130
130
  manifest: { id: 'test', label: 'test', version: '0.0.0', views: [] },
131
- activate(ctx) { captured = ctx; },
131
+ register(ctx) { captured = ctx; },
132
132
  });
133
133
  await activateShard('test');
134
134
  expect(captured.resolveUrl('api/ws')).toBe('https://example.com/api/ws');