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,6 +1,6 @@
1
1
  import type { StateZones } from '../state/zones.svelte';
2
2
  import type { ZoneSchema, ZoneManager } from '../state/types';
3
- import type { DocumentHandle, DocumentHandleOptions } from '../documents/types';
3
+ import type { DocumentHandle } from '../documents/types';
4
4
  import type { BrowseCapability } from '../documents/browse';
5
5
  import type { DocumentPickerApi } from '../documents/picker-api';
6
6
  import type { EnvState } from '../env/types';
@@ -91,21 +91,6 @@ export interface MountContext {
91
91
  */
92
92
  location(): TreeRootRef | null;
93
93
  }
94
- /**
95
- * Passed to `Shard.onAppActivate`. The `documents()` factory remains for
96
- * back-compat (it returns a handle scoped to the app namespace, identical
97
- * to what `ctx.documents()` now returns by default). Prefer `ctx.documents()`
98
- * in new code — the framework auto-scopes the shard's existing handle to
99
- * the active app's namespace via `shardAppBindings`.
100
- *
101
- * @deprecated Use `ctx.documents()` — auto-binds to the active app.
102
- */
103
- export interface AppActivateContext {
104
- /** The id of the app that just became active. */
105
- readonly appId: string;
106
- /** @deprecated Use `ctx.documents()`. */
107
- documents(options?: DocumentHandleOptions): DocumentHandle;
108
- }
109
94
  /**
110
95
  * The shard-side adapter that knows how to bring a view to life inside a
111
96
  * given HTMLElement. The container is owned by the framework (the slot);
@@ -239,8 +224,13 @@ export interface ShardContext {
239
224
  * @param verb - The verb definition (name, summary, run function).
240
225
  */
241
226
  registerVerb(verb: Verb): void;
242
- /** Obtain a file-oriented document handle scoped to this shard. */
243
- documents(options: DocumentHandleOptions): DocumentHandle;
227
+ /**
228
+ * Pre-minted document handle for this shard. Format moves per-call via
229
+ * readText/writeText/readJson/writeJson/readBinary/writeBinary. The
230
+ * handle's namespace resolves lazily on every operation — `{shardId}`
231
+ * when no app is bound, `{appId}` while a required-of app is active.
232
+ */
233
+ documents: DocumentHandle;
244
234
  /**
245
235
  * Cross-origin-safe HTTP helper. Resolves relative `/api/...` paths
246
236
  * against the configured serverUrl. In Tauri, routes through
@@ -366,18 +356,14 @@ export interface Shard {
366
356
  /** Static description of this shard's identity and declared contributions. */
367
357
  manifest: ShardManifest;
368
358
  /**
369
- * Run once when the shard is activated. Use `ctx` to declare state zones,
370
- * register view factories, and obtain document handles. Must register a
371
- * factory for every view id declared in `manifest.views`.
372
- */
373
- activate(ctx: ShardContext): void | Promise<void>;
374
- /**
375
- * @deprecated Use `registerContributions(ctx)` for static registrations
376
- * and `activate(ctx)` for imperative setup. `autostart` will be removed
377
- * in a future ADR — see ADR-026. Existing shards using `autostart` are
378
- * not broken; this is a migration signal only.
359
+ * Runs once at SH3 boot, for every registered shard, unconditionally.
360
+ * The only place to declare state zones, register views, verbs, actions,
361
+ * custom elements, contributions, and reactive effects. Everything
362
+ * registered here lives for the rest of the SH3 session.
363
+ *
364
+ * v3 contract — replaces v2's `activate()` + `autostart()` + `registerContributions()`.
379
365
  */
380
- autostart?(ctx: ShardContext): void | Promise<void>;
366
+ register(ctx: ShardContext): void | Promise<void>;
381
367
  /** Optional cleanup hook called when the shard is deactivated. Release timers, subscriptions, and external resources here. */
382
368
  deactivate?(): void | Promise<void>;
383
369
  /**
@@ -385,48 +371,29 @@ export interface Shard {
385
371
  * remains active; its views and state are preserved. Return `false`
386
372
  * (sync or async) to cancel the navigation.
387
373
  */
388
- suspend?(): void | false | Promise<void | false>;
374
+ suspend?(ctx: ShardContext): void | false | Promise<void | false>;
389
375
  /**
390
376
  * Called when the owning app resumes from Home. Receives the same
391
377
  * `ShardContext` that `activate` received.
392
378
  */
393
379
  resume?(ctx: ShardContext): void | Promise<void>;
394
380
  /** Fires when a key minted by this shard is revoked from any source. */
395
- onKeyRevoked?(id: string): void | Promise<void>;
396
- /**
397
- * Register views, commands, hotkey bindings, verbs, toolbar items, and
398
- * menu entries — anything that doesn't depend on session state. Called
399
- * once after `activate()`. Separating static registrations from ceremony
400
- * allows the framework to re-call this on hot-reload without re-running
401
- * the full activation lifecycle. All contributions registered here are
402
- * auto-unregistered when the shard deactivates.
403
- *
404
- * Shards that currently register everything inside `activate()` need not
405
- * move — `activate()` still works. Use `registerContributions()` for new
406
- * shards or when you want hot-reload-safe contribution registration.
407
- */
408
- registerContributions?(ctx: ShardContext): void;
381
+ onKeyRevoked?(ctx: ShardContext, id: string): void | Promise<void>;
409
382
  /**
410
- * Called after all required shards have activated and the app's document
411
- * namespace binding is established. Use this hook for app-context-aware
412
- * setup (e.g. preloading docs, registering app-specific contributions).
413
- * The shard's existing `ctx.documents()` handle is already auto-bound to
414
- * the app namespace there is no need to re-mint or swap handles here.
415
- *
416
- * The `appCtx.documents()` factory is retained for back-compat but is
417
- * deprecated; new code should use `ctx.documents()`.
383
+ * Called when an app that lists this shard in `requiredShards` launches.
384
+ * v3 signature: receives the shard's ctx plus the activating app's id.
385
+ * The shard's `ctx.documents` namespace has already been rotated to the
386
+ * app's namespace before this fires. Disposers from registrations made
387
+ * inside this hook are tracked in a per-app bag and auto-cleared on
388
+ * the matching `onAppDeactivate`.
418
389
  */
419
- onAppActivate?(appId: string, appCtx: AppActivateContext): void | Promise<void>;
390
+ onAppActivate?(ctx: ShardContext, appId: string): void | Promise<void>;
420
391
  /**
421
- * Called when the app this shard was part of is unloaded — a different app
422
- * is launched (the current one is force-unloaded), or `unregisterApp` is
423
- * called. NOT fired by `returnToHome` that path keeps the app alive and
424
- * uses the `suspend`/`resume` hooks instead. The shard remains active; its
425
- * background services and standalone document handles are still valid.
426
- * Use this to release app-scoped document handles and revert to standalone
427
- * state.
392
+ * Called when an app that requires this shard unloads. v3 signature:
393
+ * receives ctx + appId. The shard stays alive; only per-app
394
+ * registrations (those added inside `onAppActivate`) are torn down.
428
395
  */
429
- onAppDeactivate?(appId: string): void | Promise<void>;
396
+ onAppDeactivate?(ctx: ShardContext, appId: string): void | Promise<void>;
430
397
  /**
431
398
  * Called BEFORE layout slots begin mounting, after all required shards have
432
399
  * activated. `slots` contains every slot in the restored layout tree that
@@ -434,7 +401,7 @@ export interface Shard {
434
401
  * slot-specific contributions here (e.g. `EDITOR_DOCUMENT_POINT` keyed by
435
402
  * `slotId`) so they are in place when the view factory's `mount()` is called.
436
403
  */
437
- onLayoutWillRestore?(slots: RestoredSlot[]): void | Promise<void>;
404
+ onLayoutWillRestore?(ctx: ShardContext, slots: RestoredSlot[]): void | Promise<void>;
438
405
  /**
439
406
  * Called AFTER the layout has been switched to the app's tree and slots
440
407
  * have begun mounting. Use for post-mount wiring and reconciliation.
@@ -442,7 +409,7 @@ export interface Shard {
442
409
  * this hook fires after the layout render, not after all `mount()` calls
443
410
  * have returned.
444
411
  */
445
- onLayoutRestored?(slots: RestoredSlot[]): void;
412
+ onLayoutRestored?(ctx: ShardContext, slots: RestoredSlot[]): void;
446
413
  }
447
414
  /**
448
415
  * Source-level shape of a shard as written by external package authors.
@@ -29,7 +29,7 @@ import { getAuthToken } from '../transport/authToken';
29
29
  export { makeSh3ApiHeadless, makeSh3ApiForTest } from '../sh3Api/headless';
30
30
  export const shellShard = {
31
31
  manifest,
32
- activate(ctx) {
32
+ register(ctx) {
33
33
  registerV1Verbs(ctx);
34
34
  const shell = makeSh3ApiHeadless(ctx.zones);
35
35
  // Bind the shell-shard's workspace zone — backs scrollback persistence
@@ -85,9 +85,6 @@ export const shellShard = {
85
85
  };
86
86
  ctx.registerView('shell:terminal', factory);
87
87
  },
88
- autostart() {
89
- // Intentionally empty — same pattern as __sh3core__.
90
- },
91
88
  deactivate() {
92
89
  __unbindZone();
93
90
  },
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Auto-generated from package.json — do not edit manually. */
2
- export declare const VERSION = "0.22.0";
2
+ export declare const VERSION = "0.22.2";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Auto-generated from package.json — do not edit manually. */
2
- export const VERSION = '0.22.0';
2
+ export const VERSION = '0.22.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh3-core",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -1,120 +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
- import { PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ, PERMISSION_DOCUMENTS_WRITE, } from '../documents/types';
6
- describe('ctx.browse permission gating', () => {
7
- beforeEach(() => {
8
- __resetShardRegistryForTest();
9
- __setDocumentBackend(new MemoryDocumentBackend());
10
- __setActiveScope('tenant-a');
11
- });
12
- it('is undefined when no documents permission is declared', async () => {
13
- let captured = null;
14
- registerShard({
15
- manifest: { id: 'no-browse', label: 'n', version: '0.0.0', views: [] },
16
- activate(ctx) { captured = ctx; },
17
- });
18
- await activateShard('no-browse');
19
- expect(captured.browse).toBeUndefined();
20
- });
21
- it('exposes metadata methods when documents:browse is declared', async () => {
22
- var _a, _b, _c;
23
- let captured = null;
24
- registerShard({
25
- manifest: {
26
- id: 'has-browse', label: 'b', version: '0.0.0', views: [],
27
- permissions: [PERMISSION_DOCUMENTS_BROWSE],
28
- },
29
- activate(ctx) { captured = ctx; },
30
- });
31
- await activateShard('has-browse');
32
- expect(typeof ((_a = captured.browse) === null || _a === void 0 ? void 0 : _a.listDocuments)).toBe('function');
33
- expect(typeof ((_b = captured.browse) === null || _b === void 0 ? void 0 : _b.watchDocuments)).toBe('function');
34
- expect(typeof ((_c = captured.browse) === null || _c === void 0 ? void 0 : _c.listShards)).toBe('function');
35
- });
36
- it('omits readFrom and writeTo when only documents:browse is declared', async () => {
37
- var _a, _b;
38
- let captured = null;
39
- registerShard({
40
- manifest: {
41
- id: 'browse-only', label: 'b', version: '0.0.0', views: [],
42
- permissions: [PERMISSION_DOCUMENTS_BROWSE],
43
- },
44
- activate(ctx) { captured = ctx; },
45
- });
46
- await activateShard('browse-only');
47
- expect((_a = captured.browse) === null || _a === void 0 ? void 0 : _a.readFrom).toBeUndefined();
48
- expect((_b = captured.browse) === null || _b === void 0 ? void 0 : _b.writeTo).toBeUndefined();
49
- });
50
- it('exposes readFrom when documents:browse + documents:read are declared', async () => {
51
- var _a, _b;
52
- let captured = null;
53
- registerShard({
54
- manifest: {
55
- id: 'browse-and-read', label: 'br', version: '0.0.0', views: [],
56
- permissions: [PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ],
57
- },
58
- activate(ctx) { captured = ctx; },
59
- });
60
- await activateShard('browse-and-read');
61
- expect(typeof ((_a = captured.browse) === null || _a === void 0 ? void 0 : _a.readFrom)).toBe('function');
62
- expect((_b = captured.browse) === null || _b === void 0 ? void 0 : _b.writeTo).toBeUndefined();
63
- });
64
- it('exposes writeTo when documents:browse + documents:write are declared', async () => {
65
- var _a, _b;
66
- let captured = null;
67
- registerShard({
68
- manifest: {
69
- id: 'browse-and-write', label: 'bw', version: '0.0.0', views: [],
70
- permissions: [PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_WRITE],
71
- },
72
- activate(ctx) { captured = ctx; },
73
- });
74
- await activateShard('browse-and-write');
75
- expect((_a = captured.browse) === null || _a === void 0 ? void 0 : _a.readFrom).toBeUndefined();
76
- expect(typeof ((_b = captured.browse) === null || _b === void 0 ? void 0 : _b.writeTo)).toBe('function');
77
- });
78
- it('exposes both readFrom and writeTo when all three permissions are declared', async () => {
79
- var _a, _b;
80
- let captured = null;
81
- registerShard({
82
- manifest: {
83
- id: 'full-access', label: 'full', version: '0.0.0', views: [],
84
- permissions: [
85
- PERMISSION_DOCUMENTS_BROWSE,
86
- PERMISSION_DOCUMENTS_READ,
87
- PERMISSION_DOCUMENTS_WRITE,
88
- ],
89
- },
90
- activate(ctx) { captured = ctx; },
91
- });
92
- await activateShard('full-access');
93
- expect(typeof ((_a = captured.browse) === null || _a === void 0 ? void 0 : _a.readFrom)).toBe('function');
94
- expect(typeof ((_b = captured.browse) === null || _b === void 0 ? void 0 : _b.writeTo)).toBe('function');
95
- });
96
- it('yields no ctx.browse when documents:read is declared without documents:browse', async () => {
97
- let captured = null;
98
- registerShard({
99
- manifest: {
100
- id: 'read-only', label: 'r', version: '0.0.0', views: [],
101
- permissions: [PERMISSION_DOCUMENTS_READ],
102
- },
103
- activate(ctx) { captured = ctx; },
104
- });
105
- await activateShard('read-only');
106
- expect(captured.browse).toBeUndefined();
107
- });
108
- it('yields no ctx.browse when documents:write is declared without documents:browse', async () => {
109
- let captured = null;
110
- registerShard({
111
- manifest: {
112
- id: 'write-only', label: 'w', version: '0.0.0', views: [],
113
- permissions: [PERMISSION_DOCUMENTS_WRITE],
114
- },
115
- activate(ctx) { captured = ctx; },
116
- });
117
- await activateShard('write-only');
118
- expect(captured.browse).toBeUndefined();
119
- });
120
- });
@@ -1,141 +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, deactivateShard, __resetShardRegistryForTest, } from './activate.svelte';
5
- import { __resetContributionsForTest, list, listPoints } from '../contributions';
6
- describe('ctx.contributions', () => {
7
- beforeEach(() => {
8
- __resetShardRegistryForTest();
9
- __resetContributionsForTest();
10
- __setDocumentBackend(new MemoryDocumentBackend());
11
- __setActiveScope('tenant-a');
12
- });
13
- it('is always present on ShardContext (no permission required)', async () => {
14
- let captured = null;
15
- registerShard({
16
- manifest: { id: 'a', label: 'A', version: '0.0.0', views: [] },
17
- activate(ctx) { captured = ctx; },
18
- });
19
- await activateShard('a');
20
- expect(typeof captured.contributions.register).toBe('function');
21
- expect(typeof captured.contributions.list).toBe('function');
22
- expect(typeof captured.contributions.listPoints).toBe('function');
23
- expect(typeof captured.contributions.onChange).toBe('function');
24
- });
25
- it('register() makes the descriptor visible via the global list()', async () => {
26
- registerShard({
27
- manifest: { id: 'provider', label: 'P', version: '0.0.0', views: [] },
28
- activate(ctx) {
29
- ctx.contributions.register('my.point', { id: 'alpha' });
30
- },
31
- });
32
- await activateShard('provider');
33
- expect(list('my.point')).toEqual([{ id: 'alpha' }]);
34
- });
35
- it('deactivate auto-unregisters every descriptor the shard registered', async () => {
36
- registerShard({
37
- manifest: { id: 'provider', label: 'P', version: '0.0.0', views: [] },
38
- activate(ctx) {
39
- ctx.contributions.register('p1', { id: 'a' });
40
- ctx.contributions.register('p1', { id: 'b' });
41
- ctx.contributions.register('p2', { id: 'c' });
42
- },
43
- });
44
- await activateShard('provider');
45
- expect(list('p1')).toHaveLength(2);
46
- expect(list('p2')).toHaveLength(1);
47
- deactivateShard('provider');
48
- expect(list('p1')).toEqual([]);
49
- expect(list('p2')).toEqual([]);
50
- expect(listPoints()).toEqual([]);
51
- });
52
- it('deactivate auto-unsubscribes every onChange listener the shard registered', async () => {
53
- const cb = vi.fn();
54
- registerShard({
55
- manifest: { id: 'watcher', label: 'W', version: '0.0.0', views: [] },
56
- activate(ctx) {
57
- ctx.contributions.onChange('p', cb);
58
- },
59
- });
60
- await activateShard('watcher');
61
- registerShard({
62
- manifest: { id: 'provider', label: 'P', version: '0.0.0', views: [] },
63
- activate(ctx) {
64
- ctx.contributions.register('p', { id: 'x' });
65
- },
66
- });
67
- await activateShard('provider');
68
- expect(cb).toHaveBeenCalledTimes(1);
69
- deactivateShard('watcher');
70
- // Further registrations must not reach the watcher's callback.
71
- registerShard({
72
- manifest: { id: 'provider-2', label: 'P2', version: '0.0.0', views: [] },
73
- activate(ctx) {
74
- ctx.contributions.register('p', { id: 'y' });
75
- },
76
- });
77
- await activateShard('provider-2');
78
- expect(cb).toHaveBeenCalledTimes(1);
79
- });
80
- it('two shards can contribute to the same point independently', async () => {
81
- registerShard({
82
- manifest: { id: 'a', label: 'A', version: '0.0.0', views: [] },
83
- activate(ctx) { ctx.contributions.register('shared', { from: 'a' }); },
84
- });
85
- registerShard({
86
- manifest: { id: 'b', label: 'B', version: '0.0.0', views: [] },
87
- activate(ctx) { ctx.contributions.register('shared', { from: 'b' }); },
88
- });
89
- await activateShard('a');
90
- await activateShard('b');
91
- expect(list('shared')).toEqual([{ from: 'a' }, { from: 'b' }]);
92
- deactivateShard('a');
93
- expect(list('shared')).toEqual([{ from: 'b' }]);
94
- });
95
- it('explicit unregister returned by register() is safe alongside auto-cleanup', async () => {
96
- let earlyUnregister;
97
- registerShard({
98
- manifest: { id: 'p', label: 'P', version: '0.0.0', views: [] },
99
- activate(ctx) {
100
- earlyUnregister = ctx.contributions.register('p', { id: 'a' });
101
- },
102
- });
103
- await activateShard('p');
104
- expect(list('p')).toHaveLength(1);
105
- earlyUnregister();
106
- expect(list('p')).toEqual([]);
107
- // Deactivate must not throw when the entry is already gone.
108
- expect(() => deactivateShard('p')).not.toThrow();
109
- });
110
- it('onAnyChange fires for register/unregister at any point and auto-unsubscribes on deactivate', async () => {
111
- const cb = vi.fn();
112
- registerShard({
113
- manifest: { id: 'watcher', label: 'W', version: '0.0.0', views: [] },
114
- activate(ctx) {
115
- ctx.contributions.onAnyChange(cb);
116
- },
117
- });
118
- await activateShard('watcher');
119
- registerShard({
120
- manifest: { id: 'p1', label: 'P1', version: '0.0.0', views: [] },
121
- activate(ctx) {
122
- ctx.contributions.register('point.a', { id: 'x' });
123
- ctx.contributions.register('point.b', { id: 'y' });
124
- },
125
- });
126
- await activateShard('p1');
127
- expect(cb).toHaveBeenCalledTimes(2);
128
- expect(cb).toHaveBeenCalledWith('point.a');
129
- expect(cb).toHaveBeenCalledWith('point.b');
130
- deactivateShard('watcher');
131
- cb.mockClear();
132
- registerShard({
133
- manifest: { id: 'p2', label: 'P2', version: '0.0.0', views: [] },
134
- activate(ctx) {
135
- ctx.contributions.register('point.c', { id: 'z' });
136
- },
137
- });
138
- await activateShard('p2');
139
- expect(cb).not.toHaveBeenCalled();
140
- });
141
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,98 +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, registeredShards, activeShards, __resetShardRegistryForTest, erroredShards, } from './activate.svelte';
5
- describe('erroredShards map', () => {
6
- beforeEach(() => {
7
- __resetShardRegistryForTest();
8
- __setDocumentBackend(new MemoryDocumentBackend());
9
- __setActiveScope('tenant-a');
10
- });
11
- it('is empty after reset', () => {
12
- expect(erroredShards.size).toBe(0);
13
- });
14
- it('supports the Map read API used by callers', () => {
15
- expect(typeof erroredShards.has).toBe('function');
16
- expect(typeof erroredShards.get).toBe('function');
17
- expect(erroredShards.has('anything')).toBe(false);
18
- });
19
- });
20
- describe('activateShard — unwind on activation failure', () => {
21
- beforeEach(() => {
22
- __resetShardRegistryForTest();
23
- __setDocumentBackend(new MemoryDocumentBackend());
24
- __setActiveScope('tenant-a');
25
- });
26
- it('unwinds partial state and records the error when activate throws', async () => {
27
- const shard = {
28
- manifest: {
29
- id: 'broken',
30
- label: 'Broken',
31
- version: '0.0.0',
32
- views: [],
33
- },
34
- activate(ctx) {
35
- ctx.registerView('broken:view', { mount: () => ({ unmount() { } }) });
36
- throw new Error('dependency missing');
37
- },
38
- };
39
- registerShard(shard);
40
- await expect(activateShard('broken')).rejects.toThrow('dependency missing');
41
- expect(activeShards.has('broken')).toBe(false);
42
- expect(registeredShards.has('broken')).toBe(true);
43
- const entry = erroredShards.get('broken');
44
- expect(entry).toBeDefined();
45
- expect(entry === null || entry === void 0 ? void 0 : entry.id).toBe('broken');
46
- expect(entry === null || entry === void 0 ? void 0 : entry.phase).toBe('launch');
47
- expect(entry === null || entry === void 0 ? void 0 : entry.error).toBeInstanceOf(Error);
48
- expect(typeof (entry === null || entry === void 0 ? void 0 : entry.timestamp)).toBe('number');
49
- const { getView } = await import('./registry');
50
- expect(getView('broken:view')).toBeUndefined();
51
- });
52
- it('records phase "autostart" when called with that option', async () => {
53
- var _a;
54
- const shard = {
55
- manifest: { id: 'broken-auto', label: 'B', version: '0.0.0', views: [] },
56
- activate() {
57
- throw new Error('no');
58
- },
59
- };
60
- registerShard(shard);
61
- await expect(activateShard('broken-auto', { phase: 'autostart' })).rejects.toThrow('no');
62
- expect((_a = erroredShards.get('broken-auto')) === null || _a === void 0 ? void 0 : _a.phase).toBe('autostart');
63
- });
64
- it('clears the error entry when the shard is re-registered', async () => {
65
- const broken = {
66
- manifest: { id: 'reborn', label: 'R', version: '0.0.0', views: [] },
67
- activate() {
68
- throw new Error('first try');
69
- },
70
- };
71
- registerShard(broken);
72
- await expect(activateShard('reborn')).rejects.toThrow('first try');
73
- expect(erroredShards.has('reborn')).toBe(true);
74
- const fixed = {
75
- manifest: { id: 'reborn', label: 'R', version: '0.0.1', views: [] },
76
- activate() { },
77
- };
78
- registerShard(fixed);
79
- expect(erroredShards.has('reborn')).toBe(false);
80
- });
81
- it('clears the error entry when activation eventually succeeds', async () => {
82
- let shouldFail = true;
83
- const shard = {
84
- manifest: { id: 'flaky', label: 'F', version: '0.0.0', views: [] },
85
- activate() {
86
- if (shouldFail)
87
- throw new Error('first try');
88
- },
89
- };
90
- registerShard(shard);
91
- await expect(activateShard('flaky')).rejects.toThrow('first try');
92
- expect(erroredShards.has('flaky')).toBe(true);
93
- shouldFail = false;
94
- await activateShard('flaky');
95
- expect(erroredShards.has('flaky')).toBe(false);
96
- expect(activeShards.has('flaky')).toBe(true);
97
- });
98
- });
@@ -1 +0,0 @@
1
- export {};