sh3-core 0.22.0 → 0.22.2

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 (80) hide show
  1. package/dist/__test__/fixtures.js +1 -1
  2. package/dist/__test__/reset.js +1 -3
  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 +4 -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 +49 -64
  25. package/dist/apps/lifecycle.test.js +30 -76
  26. package/dist/conflicts/adapter-documents.js +1 -2
  27. package/dist/createShell.js +1 -1
  28. package/dist/documents/handle.d.ts +9 -4
  29. package/dist/documents/handle.js +40 -29
  30. package/dist/documents/handle.test.js +60 -51
  31. package/dist/documents/index.d.ts +1 -1
  32. package/dist/documents/types.d.ts +16 -26
  33. package/dist/host.d.ts +1 -1
  34. package/dist/host.js +9 -56
  35. package/dist/host.svelte.test.js +31 -63
  36. package/dist/layouts-shard/LayoutsSection.svelte +1 -1
  37. package/dist/layouts-shard/layoutsShard.svelte.js +2 -5
  38. package/dist/layouts-shard/layoutsShard.svelte.test.js +2 -2
  39. package/dist/projects-shard/projectsShard.svelte.js +1 -5
  40. package/dist/registry/installer.js +1 -1
  41. package/dist/registry/loader.d.ts +1 -1
  42. package/dist/registry/loader.js +3 -3
  43. package/dist/registry/permission-descriptions.test.js +2 -2
  44. package/dist/registry/register.js +1 -1
  45. package/dist/registry/register.test.js +1 -1
  46. package/dist/runtime/runVerb-shell.test.js +1 -1
  47. package/dist/runtime/runVerb.js +2 -2
  48. package/dist/runtime/runVerb.test.js +9 -9
  49. package/dist/server-shard/types.d.ts +56 -0
  50. package/dist/sh3Api/headless.js +1 -1
  51. package/dist/sh3core-shard/sh3coreShard.svelte.js +1 -6
  52. package/dist/shards/ctx-fetch.test.js +9 -9
  53. package/dist/shards/lifecycle.svelte.d.ts +108 -0
  54. package/dist/shards/lifecycle.svelte.js +551 -0
  55. package/dist/shards/lifecycle.test.js +139 -0
  56. package/dist/shards/types.d.ts +30 -63
  57. package/dist/shell-shard/shellShard.svelte.js +1 -4
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/package.json +1 -1
  61. package/dist/shards/activate-browse.test.js +0 -120
  62. package/dist/shards/activate-contributions.test.js +0 -141
  63. package/dist/shards/activate-error-isolation.test.d.ts +0 -1
  64. package/dist/shards/activate-error-isolation.test.js +0 -98
  65. package/dist/shards/activate-fields.svelte.test.d.ts +0 -1
  66. package/dist/shards/activate-fields.svelte.test.js +0 -121
  67. package/dist/shards/activate-on-key-revoked.test.d.ts +0 -1
  68. package/dist/shards/activate-on-key-revoked.test.js +0 -60
  69. package/dist/shards/activate-runtime.test.d.ts +0 -1
  70. package/dist/shards/activate-runtime.test.js +0 -344
  71. package/dist/shards/activate-scopeid.test.d.ts +0 -1
  72. package/dist/shards/activate-scopeid.test.js +0 -21
  73. package/dist/shards/activate.svelte.d.ts +0 -102
  74. package/dist/shards/activate.svelte.js +0 -407
  75. package/dist/shards/app-binding.svelte.d.ts +0 -8
  76. package/dist/shards/app-binding.svelte.js +0 -30
  77. package/dist/shards/app-binding.test.d.ts +0 -1
  78. package/dist/shards/app-binding.test.js +0 -25
  79. /package/dist/{shards/activate-browse.test.d.ts → actions/dispatcher-v3.test.d.ts} +0 -0
  80. /package/dist/shards/{activate-contributions.test.d.ts → lifecycle.test.d.ts} +0 -0
@@ -1,121 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { registerShard, activateShard, __resetShardRegistryForTest, } from './activate.svelte';
3
- import { __resetViewRegistryForTest } from './registry';
4
- import { __resetContributionsForTest } from '../contributions/registry';
5
- import { __resetDecorationLayerForTest } from '../fields/decoration';
6
- const makeShard = (overrides) => (Object.assign({ activate: () => { } }, overrides));
7
- describe('ctx.sh3.fields integration', () => {
8
- beforeEach(() => {
9
- __resetShardRegistryForTest();
10
- __resetViewRegistryForTest();
11
- __resetContributionsForTest();
12
- __resetDecorationLayerForTest();
13
- document.body.innerHTML = '';
14
- });
15
- it('shard-scoped field is visible to a consumer via ctx.sh3.fields.list', async () => {
16
- let value = 'dark';
17
- registerShard(makeShard({
18
- manifest: { id: 'producer', label: 'P', version: '0.0.0', views: [] },
19
- activate(ctx) {
20
- ctx.contributions.register('sh3.controllable-field', {
21
- shape: 'imperative',
22
- fieldId: 'theme',
23
- label: 'Theme',
24
- kind: 'enum',
25
- enumValues: ['light', 'dark'],
26
- get: () => value,
27
- set: (v) => { value = v; },
28
- });
29
- },
30
- }));
31
- let consumerCtx = null;
32
- registerShard(makeShard({
33
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
34
- activate(ctx) { consumerCtx = ctx; },
35
- }));
36
- await activateShard('producer');
37
- await activateShard('consumer');
38
- const list = consumerCtx.sh3.fields.list();
39
- expect(list).toHaveLength(1);
40
- expect(list[0]).toMatchObject({
41
- shardId: 'producer',
42
- fieldId: 'theme',
43
- kind: 'enum',
44
- readonly: false,
45
- source: 'contributed',
46
- });
47
- expect(consumerCtx.sh3.fields.get({ shardId: 'producer', fieldId: 'theme' })).toBe('dark');
48
- await consumerCtx.sh3.fields.set({ shardId: 'producer', fieldId: 'theme' }, 'light');
49
- expect(value).toBe('light');
50
- });
51
- it('contenteditable-style imperative+element pattern works end-to-end (set, decorate, unmount)', async () => {
52
- const editorEl = document.createElement('div');
53
- editorEl.contentEditable = 'true';
54
- editorEl.textContent = 'initial';
55
- document.body.appendChild(editorEl);
56
- editorEl.getBoundingClientRect = () => ({ x: 5, y: 5, width: 100, height: 50, top: 5, left: 5, right: 105, bottom: 55 });
57
- const set = vi.fn().mockImplementation((v) => { editorEl.textContent = String(v); });
58
- registerShard(makeShard({
59
- manifest: { id: 'producer', label: 'P', version: '0.0.0', views: [] },
60
- activate(ctx) {
61
- ctx.contributions.register('sh3.controllable-field', {
62
- shape: 'imperative',
63
- fieldId: 'body',
64
- label: 'Body',
65
- kind: 'string',
66
- get: () => { var _a; return (_a = editorEl.textContent) !== null && _a !== void 0 ? _a : ''; },
67
- set,
68
- element: editorEl,
69
- });
70
- },
71
- }));
72
- let consumerCtx = null;
73
- registerShard(makeShard({
74
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
75
- activate(ctx) { consumerCtx = ctx; },
76
- }));
77
- await activateShard('producer');
78
- await activateShard('consumer');
79
- await consumerCtx.sh3.fields.set({ shardId: 'producer', fieldId: 'body' }, 'hello');
80
- expect(set).toHaveBeenCalledWith('hello');
81
- expect(editorEl.textContent).toBe('hello');
82
- const off = consumerCtx.sh3.fields.attachDecoration({ shardId: 'producer', fieldId: 'body' }, () => {
83
- const badge = document.createElement('span');
84
- badge.dataset.kind = 'sh3-ai-badge';
85
- return badge;
86
- });
87
- expect(document.querySelector('[data-kind="sh3-ai-badge"]')).not.toBeNull();
88
- off();
89
- expect(document.querySelector('[data-kind="sh3-ai-badge"]')).toBeNull();
90
- });
91
- it('listFields filters by shardId', async () => {
92
- registerShard(makeShard({
93
- manifest: { id: 'a', label: 'A', version: '0.0.0', views: [] },
94
- activate(ctx) {
95
- ctx.contributions.register('sh3.controllable-field', {
96
- shape: 'imperative', fieldId: 'x', label: 'X', kind: 'string',
97
- get: () => '1',
98
- });
99
- },
100
- }));
101
- registerShard(makeShard({
102
- manifest: { id: 'b', label: 'B', version: '0.0.0', views: [] },
103
- activate(ctx) {
104
- ctx.contributions.register('sh3.controllable-field', {
105
- shape: 'imperative', fieldId: 'y', label: 'Y', kind: 'string',
106
- get: () => '2',
107
- });
108
- },
109
- }));
110
- let consumerCtx = null;
111
- registerShard(makeShard({
112
- manifest: { id: 'c', label: 'C', version: '0.0.0', views: [] },
113
- activate(ctx) { consumerCtx = ctx; },
114
- }));
115
- await activateShard('a');
116
- await activateShard('b');
117
- await activateShard('c');
118
- expect(consumerCtx.sh3.fields.list({ shardId: 'a' }).map((f) => f.fieldId)).toEqual(['x']);
119
- expect(consumerCtx.sh3.fields.list({ shardId: 'b' }).map((f) => f.fieldId)).toEqual(['y']);
120
- });
121
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,60 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { MemoryDocumentBackend } from '../documents/backends';
3
- import { __setDocumentBackend, __setActiveScope } from '../documents/config';
4
- import { registerShard, activateShard, deactivateShard, __resetShardRegistryForTest } from './activate.svelte';
5
- import { emit } from '../keys/revocation-bus.svelte';
6
- describe('onKeyRevoked hook wiring', () => {
7
- beforeEach(() => {
8
- __resetShardRegistryForTest();
9
- __setDocumentBackend(new MemoryDocumentBackend());
10
- __setActiveScope('tenant-a');
11
- });
12
- it('fires onKeyRevoked when the bus emits for the shard', async () => {
13
- const received = [];
14
- registerShard({
15
- manifest: { id: 'hook-shard', label: 'h', version: '0.0.0', views: [] },
16
- activate() { },
17
- onKeyRevoked(id) { received.push(id); },
18
- });
19
- await activateShard('hook-shard');
20
- emit('hook-shard', 'key-abc');
21
- // Handler is async; give microtasks a chance to flush.
22
- await Promise.resolve();
23
- expect(received).toEqual(['key-abc']);
24
- });
25
- it('does not fire for a different shardId', async () => {
26
- const received = [];
27
- registerShard({
28
- manifest: { id: 'shard-x', label: 'x', version: '0.0.0', views: [] },
29
- activate() { },
30
- onKeyRevoked(id) { received.push(id); },
31
- });
32
- await activateShard('shard-x');
33
- emit('shard-y', 'key-other');
34
- await Promise.resolve();
35
- expect(received).toHaveLength(0);
36
- });
37
- it('does not fire after deactivation', async () => {
38
- const received = [];
39
- registerShard({
40
- manifest: { id: 'shard-deact', label: 'd', version: '0.0.0', views: [] },
41
- activate() { },
42
- onKeyRevoked(id) { received.push(id); },
43
- });
44
- await activateShard('shard-deact');
45
- deactivateShard('shard-deact');
46
- emit('shard-deact', 'key-gone');
47
- await Promise.resolve();
48
- expect(received).toHaveLength(0);
49
- });
50
- it('does not subscribe when onKeyRevoked is absent', async () => {
51
- // Should not throw — just silently skips subscribing.
52
- registerShard({
53
- manifest: { id: 'no-hook', label: 'n', version: '0.0.0', views: [] },
54
- activate() { },
55
- });
56
- await expect(activateShard('no-hook')).resolves.toBeUndefined();
57
- // Emitting for a shard with no listener is a no-op.
58
- expect(() => emit('no-hook', 'k')).not.toThrow();
59
- });
60
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,344 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { MemoryDocumentBackend } from '../documents/backends';
3
- import { __setDocumentBackend, __setActiveScope } from '../documents/config';
4
- import { registerShard, activateShard, __resetShardRegistryForTest, } from './activate.svelte';
5
- import { __resetViewRegistryForTest } from './registry';
6
- function programmaticVerb(name, summary, body) {
7
- return {
8
- name,
9
- summary,
10
- programmatic: true,
11
- async run(vctx, args) {
12
- if (body)
13
- await body(vctx, args);
14
- },
15
- };
16
- }
17
- function plainVerb(name, summary) {
18
- return { name, summary, async run() { } };
19
- }
20
- describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
21
- beforeEach(() => {
22
- __resetShardRegistryForTest();
23
- __resetViewRegistryForTest();
24
- __setDocumentBackend(new MemoryDocumentBackend());
25
- __setActiveScope('tenant-test');
26
- });
27
- it('listVerbs returns every verb across active shards with shardId', async () => {
28
- registerShard({
29
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
30
- activate(ctx) {
31
- ctx.registerVerb(programmaticVerb('a', 'first verb'));
32
- ctx.registerVerb(plainVerb('b', 'second verb'));
33
- },
34
- });
35
- let consumerCtx = null;
36
- registerShard({
37
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
38
- activate(ctx) {
39
- consumerCtx = ctx;
40
- },
41
- });
42
- await activateShard('host');
43
- await activateShard('consumer');
44
- const list = consumerCtx.sh3.listVerbs();
45
- const a = list.find((v) => v.name === 'host:a');
46
- const b = list.find((v) => v.name === 'host:b');
47
- expect(a).toEqual({ shardId: 'host', name: 'host:a', summary: 'first verb', programmatic: true, schema: undefined });
48
- expect(b).toEqual({ shardId: 'host', name: 'host:b', summary: 'second verb', programmatic: undefined, schema: undefined });
49
- });
50
- it('listVerbs({ programmaticOnly: true }) returns only verbs that opted in', async () => {
51
- registerShard({
52
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
53
- activate(ctx) {
54
- ctx.registerVerb(programmaticVerb('a', 'first verb'));
55
- ctx.registerVerb(plainVerb('b', 'second verb'));
56
- },
57
- });
58
- let consumerCtx = null;
59
- registerShard({
60
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
61
- activate(ctx) {
62
- consumerCtx = ctx;
63
- },
64
- });
65
- await activateShard('host');
66
- await activateShard('consumer');
67
- const list = consumerCtx.sh3.listVerbs({ programmaticOnly: true });
68
- expect(list.find((v) => v.name === 'host:a')).toBeDefined();
69
- expect(list.find((v) => v.name === 'host:b')).toBeUndefined();
70
- expect(list.every((v) => v.programmatic === true)).toBe(true);
71
- });
72
- it('runVerb dispatches a programmatic verb and returns captured scrollback', async () => {
73
- registerShard({
74
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
75
- activate(ctx) {
76
- ctx.registerVerb(programmaticVerb('echo', 'echoes args', async (vctx, args) => {
77
- vctx.scrollback.push({ kind: 'status', text: `echo: ${args.join(' ')}`, level: 'info', ts: 0 });
78
- }));
79
- },
80
- });
81
- let consumerCtx = null;
82
- registerShard({
83
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
84
- activate(ctx) {
85
- consumerCtx = ctx;
86
- },
87
- });
88
- await activateShard('host');
89
- await activateShard('consumer');
90
- const out = await consumerCtx.sh3.runVerb('host', 'host:echo', ['hello']);
91
- expect(out.scrollback).toHaveLength(1);
92
- expect(out.scrollback[0]).toMatchObject({ kind: 'status', text: 'echo: hello' });
93
- });
94
- it('runVerb rejects when the target verb is not programmatic', async () => {
95
- registerShard({
96
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
97
- activate(ctx) {
98
- ctx.registerVerb(plainVerb('plain', 'no opt-in'));
99
- },
100
- });
101
- let consumerCtx = null;
102
- registerShard({
103
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
104
- activate(ctx) {
105
- consumerCtx = ctx;
106
- },
107
- });
108
- await activateShard('host');
109
- await activateShard('consumer');
110
- await expect(consumerCtx.sh3.runVerb('host', 'host:plain', [])).rejects.toThrow('verb "host:plain" is not programmatic');
111
- });
112
- it('runVerb populates ctx.structuredArgs when opts.structured is set', async () => {
113
- let observed = undefined;
114
- registerShard({
115
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
116
- activate(ctx) {
117
- ctx.registerVerb(programmaticVerb('schemaCheck', 'reads structuredArgs', async (vctx) => {
118
- observed = vctx.structuredArgs;
119
- }));
120
- },
121
- });
122
- let consumerCtx = null;
123
- registerShard({
124
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
125
- activate(ctx) {
126
- consumerCtx = ctx;
127
- },
128
- });
129
- await activateShard('host');
130
- await activateShard('consumer');
131
- await consumerCtx.sh3.runVerb('host', 'host:schemaCheck', [], { structured: { foo: 'bar' } });
132
- expect(observed).toEqual({ foo: 'bar' });
133
- });
134
- it('runVerb rejects on unknown shardId', async () => {
135
- let consumerCtx = null;
136
- registerShard({
137
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
138
- activate(ctx) {
139
- consumerCtx = ctx;
140
- },
141
- });
142
- await activateShard('consumer');
143
- await expect(consumerCtx.sh3.runVerb('missing', 'x', [])).rejects.toThrow('unknown shard: missing');
144
- });
145
- it('runVerb rejects on unknown verb', async () => {
146
- registerShard({
147
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
148
- activate(ctx) {
149
- ctx.registerVerb(programmaticVerb('present', 'exists'));
150
- },
151
- });
152
- let consumerCtx = null;
153
- registerShard({
154
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
155
- activate(ctx) {
156
- consumerCtx = ctx;
157
- },
158
- });
159
- await activateShard('host');
160
- await activateShard('consumer');
161
- await expect(consumerCtx.sh3.runVerb('host', 'host:absent', [])).rejects.toThrow('unknown verb: host:absent');
162
- });
163
- it('verbs declaring schema.input expose it via listVerbs', async () => {
164
- registerShard({
165
- manifest: { id: 'host', label: 'Host', version: '0.0.0', views: [] },
166
- activate(ctx) {
167
- ctx.registerVerb({
168
- name: 'typed',
169
- summary: 'has schema',
170
- programmatic: true,
171
- schema: {
172
- input: {
173
- type: 'object',
174
- properties: { msg: { type: 'string', description: 'message' } },
175
- required: ['msg'],
176
- },
177
- },
178
- async run() { },
179
- });
180
- },
181
- });
182
- let consumerCtx = null;
183
- registerShard({
184
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
185
- activate(ctx) {
186
- consumerCtx = ctx;
187
- },
188
- });
189
- await activateShard('host');
190
- await activateShard('consumer');
191
- const list = consumerCtx.sh3.listVerbs();
192
- const typed = list.find((v) => v.name === 'host:typed');
193
- expect(typed === null || typed === void 0 ? void 0 : typed.schema).toEqual({
194
- input: {
195
- type: 'object',
196
- properties: { msg: { type: 'string', description: 'message' } },
197
- required: ['msg'],
198
- },
199
- });
200
- });
201
- it('verbNamespace overrides the shard-id prefix', async () => {
202
- registerShard({
203
- manifest: {
204
- id: 'sh3-dirt-viewer',
205
- label: 'Dirt',
206
- version: '0.0.0',
207
- views: [],
208
- verbNamespace: 'dirt',
209
- },
210
- activate(ctx) {
211
- ctx.registerVerb(plainVerb('publish', 'publish runtime'));
212
- },
213
- });
214
- let consumerCtx = null;
215
- registerShard({
216
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
217
- activate(ctx) { consumerCtx = ctx; },
218
- });
219
- await activateShard('sh3-dirt-viewer');
220
- await activateShard('consumer');
221
- const names = consumerCtx.sh3.listVerbs().map((v) => v.name);
222
- expect(names).toContain('dirt:publish');
223
- expect(names.find((n) => n.startsWith('sh3-dirt-viewer:'))).toBeUndefined();
224
- });
225
- it('verbNamespace collision across shards: first wins, second warns', async () => {
226
- var _a;
227
- registerShard({
228
- manifest: {
229
- id: 'shard-a',
230
- label: 'A',
231
- version: '0.0.0',
232
- views: [],
233
- verbNamespace: 'shared',
234
- },
235
- activate(ctx) {
236
- ctx.registerVerb(plainVerb('go', 'A go'));
237
- },
238
- });
239
- registerShard({
240
- manifest: {
241
- id: 'shard-b',
242
- label: 'B',
243
- version: '0.0.0',
244
- views: [],
245
- verbNamespace: 'shared',
246
- },
247
- activate(ctx) {
248
- // Collides with shard-a's 'shared:go'; should be skipped.
249
- ctx.registerVerb(plainVerb('go', 'B go'));
250
- // Distinct name in same namespace should still register.
251
- ctx.registerVerb(plainVerb('only-b', 'B-only verb'));
252
- },
253
- });
254
- let consumerCtx = null;
255
- registerShard({
256
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
257
- activate(ctx) { consumerCtx = ctx; },
258
- });
259
- await activateShard('shard-a');
260
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
261
- await activateShard('shard-b');
262
- await activateShard('consumer');
263
- expect(warn).toHaveBeenCalled();
264
- expect(warn.mock.calls.some((c) => /shared:go/.test(String(c[0])))).toBe(true);
265
- warn.mockRestore();
266
- const list = consumerCtx.sh3.listVerbs();
267
- const shared = list.find((v) => v.name === 'shared:go');
268
- // The losing registration must not have been tracked; deactivating
269
- // shard-b later (covered elsewhere) would otherwise remove A's verb.
270
- expect(shared === null || shared === void 0 ? void 0 : shared.summary).toBe('A go');
271
- expect(shared === null || shared === void 0 ? void 0 : shared.shardId).toBe('shard-a');
272
- expect((_a = list.find((v) => v.name === 'shared:only-b')) === null || _a === void 0 ? void 0 : _a.shardId).toBe('shard-b');
273
- });
274
- it('shell shard registers bare names regardless of verbNamespace setting', async () => {
275
- registerShard({
276
- manifest: {
277
- id: 'shell',
278
- label: 'Shell',
279
- version: '0.0.0',
280
- views: [],
281
- // Intentionally set — should be ignored for the shell shard.
282
- verbNamespace: 'ignored',
283
- },
284
- activate(ctx) {
285
- ctx.registerVerb(plainVerb('clear', 'clear scrollback'));
286
- },
287
- });
288
- let consumerCtx = null;
289
- registerShard({
290
- manifest: { id: 'consumer', label: 'C', version: '0.0.0', views: [] },
291
- activate(ctx) { consumerCtx = ctx; },
292
- });
293
- await activateShard('shell');
294
- await activateShard('consumer');
295
- const names = consumerCtx.sh3.listVerbs().map((v) => v.name);
296
- expect(names).toContain('clear');
297
- expect(names.find((n) => n.startsWith('ignored:'))).toBeUndefined();
298
- });
299
- });
300
- // ─── registerContributions ────────────────────────────────────────────────────
301
- describe('registerContributions hook', () => {
302
- beforeEach(() => {
303
- __resetShardRegistryForTest();
304
- __resetViewRegistryForTest();
305
- __setDocumentBackend(new MemoryDocumentBackend());
306
- __setActiveScope('tenant-test');
307
- });
308
- it('is called after activate() with the same ShardContext', async () => {
309
- const order = [];
310
- let activateCtx = null;
311
- let registerCtx = null;
312
- registerShard({
313
- manifest: { id: 'rc-shard', label: 'RC', version: '0.0.0', views: [] },
314
- activate(ctx) {
315
- activateCtx = ctx;
316
- order.push('activate');
317
- },
318
- registerContributions(ctx) {
319
- registerCtx = ctx;
320
- order.push('registerContributions');
321
- },
322
- });
323
- await activateShard('rc-shard');
324
- expect(order).toEqual(['activate', 'registerContributions']);
325
- expect(registerCtx).toBe(activateCtx);
326
- });
327
- it('is not called when the hook is absent', async () => {
328
- registerShard({
329
- manifest: { id: 'no-rc', label: 'NoRC', version: '0.0.0', views: [] },
330
- activate() { },
331
- });
332
- await expect(activateShard('no-rc')).resolves.toBeUndefined();
333
- });
334
- it('is not called when activate() throws', async () => {
335
- const registerContributions = vi.fn();
336
- registerShard({
337
- manifest: { id: 'fail-rc', label: 'FailRC', version: '0.0.0', views: [] },
338
- activate() { throw new Error('boom'); },
339
- registerContributions,
340
- });
341
- await expect(activateShard('fail-rc')).rejects.toThrow('boom');
342
- expect(registerContributions).not.toHaveBeenCalled();
343
- });
344
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { MemoryDocumentBackend } from '../documents/backends';
3
- import { __setDocumentBackend, __setActiveScope } from '../documents/config';
4
- import { registerShard, activateShard, __resetShardRegistryForTest } from './activate.svelte';
5
- describe('ctx.tenantId reflects active scope', () => {
6
- beforeEach(() => {
7
- __resetShardRegistryForTest();
8
- __setDocumentBackend(new MemoryDocumentBackend());
9
- __setActiveScope('scope-a');
10
- });
11
- it('is present unconditionally on ctx', async () => {
12
- let captured = null;
13
- const shard = {
14
- manifest: { id: 'test-scopeid', label: 't', version: '0.0.0', views: [] },
15
- activate(ctx) { captured = ctx; },
16
- };
17
- registerShard(shard);
18
- await activateShard('test-scopeid');
19
- expect(captured.tenantId).toBe('scope-a');
20
- });
21
- });
@@ -1,102 +0,0 @@
1
- import type { Shard, ShardContext } from './types';
2
- /**
3
- * Reactive registry of every shard known to the host. Keys are shard ids.
4
- * Populated once at boot by the glob-discovery loop in main.ts (through
5
- * `registerShard`); a future runtime loader appends late without touching
6
- * the rest of the framework. Callers that need a live list subscribe via
7
- * Svelte reactivity (e.g. sh3 home listing apps cross-references this
8
- * map to show which shards they require).
9
- */
10
- export declare const registeredShards: Map<string, Shard>;
11
- export declare const activeShards: Map<string, Shard>;
12
- /**
13
- * Reactive map of shard ids that failed to activate. Populated by
14
- * `activateShard`'s catch block; cleared when the shard is successfully
15
- * re-registered or activated. Read-only for shards; intended for diagnostic
16
- * and admin tooling that wants to surface broken shards to the user.
17
- */
18
- export interface ShardErrorEntry {
19
- id: string;
20
- error: unknown;
21
- phase: 'autostart' | 'launch' | 'satellite';
22
- timestamp: number;
23
- }
24
- export declare const erroredShards: Map<string, ShardErrorEntry>;
25
- /** Host-only. Register a callback that resolves whether the current scope
26
- * is a personal tenant or an active project. Wired by createShell after
27
- * bootstrap. Avoids circular dependencies with session-state. */
28
- export declare function __setScopeResolver(resolver: (() => 'tenant' | 'project') | null): void;
29
- /**
30
- * Register (or re-register) a shard with the framework so it can later be
31
- * activated. Records the shard in `registeredShards` but does not run
32
- * `activate` — that happens at app launch (or for self-starting shards).
33
- *
34
- * If a shard with the same id already exists it is silently replaced,
35
- * which is the expected path during package updates. If the old shard was
36
- * active it is deactivated first so the new version can be cleanly
37
- * activated on next launch.
38
- */
39
- export declare function registerShard(shard: Shard): void;
40
- export interface ActivateShardOpts {
41
- /**
42
- * Where this activation was initiated from. Determines the `phase` field
43
- * recorded in `erroredShards` if activation fails. Defaults to 'launch'
44
- * (the common case — required by an app being launched).
45
- */
46
- phase?: 'autostart' | 'launch' | 'satellite';
47
- }
48
- /**
49
- * Activate a registered shard. Builds a `ShardContext`, calls `shard.activate`,
50
- * verifies that every view declared in the manifest received a factory, then
51
- * calls `shard.autostart` if defined. Idempotent — calling on an already-active
52
- * shard is a no-op.
53
- *
54
- * If `shard.activate` throws, partial state (registered views, verbs,
55
- * contributions, document handles, actions, env subscription) is unwound
56
- * and the failure is recorded in `erroredShards` before the error is
57
- * re-thrown. Callers in `host.ts` (autostart loop) and `launchApp`
58
- * (required-shard loop) decide how to react.
59
- *
60
- * @param id - The `ShardManifest.id` of the shard to activate. Must be registered.
61
- * @param opts - Optional. `phase` is recorded in `erroredShards` on failure (default 'launch').
62
- * @throws If the shard is not registered, if `shard.activate` throws, or if a manifest view has no factory after activation.
63
- */
64
- export declare function activateShard(id: string, opts?: ActivateShardOpts): Promise<void>;
65
- /**
66
- * Deactivate an active shard. Calls `shard.deactivate`, flushes and disposes
67
- * all document handles, unregisters all view factories, and removes the shard
68
- * from `activeShards`. The shard remains in `registeredShards` and can be
69
- * re-activated. No-op if the shard is not currently active.
70
- *
71
- * @param id - The `ShardManifest.id` of the shard to deactivate.
72
- */
73
- export declare function deactivateShard(id: string): void;
74
- /**
75
- * Return true if the shard with the given id is currently active.
76
- *
77
- * @param id - The `ShardManifest.id` to check.
78
- */
79
- export declare function isActive(id: string): boolean;
80
- /**
81
- * Return the ShardContext for an active shard, or undefined if not active.
82
- * Used by lifecycle.ts to pass context to `shard.resume()`.
83
- */
84
- export declare function getShardContext(id: string): ShardContext | undefined;
85
- /**
86
- * Enumerate every view declared as `standalone` across the currently
87
- * active shards. Intended for the `views --standalone` verb and any
88
- * launcher UI that wants to surface "summonable" primitives. Only
89
- * pulls from `activeShards` — registered-but-inactive shards aren't
90
- * ready to mount.
91
- */
92
- export declare function listStandaloneViews(): Array<{
93
- shardId: string;
94
- viewId: string;
95
- label: string;
96
- }>;
97
- /**
98
- * Test-only reset. Tears down any active shard entries (without running
99
- * deactivate hooks — tests should run deactivate explicitly if they care)
100
- * and clears both registered and active maps.
101
- */
102
- export declare function __resetShardRegistryForTest(): void;