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.
- package/dist/__test__/fixtures.js +1 -1
- package/dist/__test__/reset.js +1 -1
- package/dist/__test__/smoke.test.js +2 -2
- package/dist/actions/contextMenuModel.test.js +6 -3
- package/dist/actions/ctx-actions.svelte.test.js +9 -9
- package/dist/actions/dispatcher-v3.test.js +8 -0
- package/dist/actions/dispatcher.svelte.d.ts +1 -2
- package/dist/actions/dispatcher.svelte.js +6 -7
- package/dist/actions/dispatcher.test.js +9 -12
- package/dist/actions/listActionsFromEntries.test.js +1 -2
- package/dist/actions/listActive.test.js +2 -3
- package/dist/actions/menuBarModel.test.js +1 -7
- package/dist/actions/paletteModel.test.js +1 -3
- package/dist/actions/scope-helpers.test.js +4 -4
- package/dist/actions/shardContext.test.js +2 -2
- package/dist/actions/state.svelte.d.ts +12 -2
- package/dist/actions/state.svelte.js +15 -12
- package/dist/actions/state.test.js +4 -4
- package/dist/api.d.ts +3 -3
- package/dist/api.js +1 -1
- package/dist/app/admin/adminShard.svelte.js +1 -1
- package/dist/app/store/storeShard.svelte.js +10 -5
- package/dist/app-appearance/appearanceShard.svelte.js +1 -5
- package/dist/apps/lifecycle.js +65 -33
- package/dist/apps/lifecycle.test.js +198 -10
- package/dist/artifact.d.ts +2 -0
- package/dist/build.js +1 -1
- package/dist/conflicts/adapter-documents.js +1 -2
- package/dist/createShell.js +1 -1
- package/dist/documents/handle.d.ts +9 -4
- package/dist/documents/handle.js +69 -45
- package/dist/documents/handle.test.js +99 -27
- package/dist/documents/index.d.ts +1 -1
- package/dist/documents/types.d.ts +16 -20
- package/dist/host.d.ts +1 -1
- package/dist/host.js +9 -56
- package/dist/host.svelte.test.js +31 -63
- package/dist/layout/LayoutRenderer.svelte +1 -1
- package/dist/layout/SlotContainer.svelte +1 -0
- package/dist/layout/inspection.js +19 -14
- package/dist/layout/inspection.svelte.test.js +136 -1
- package/dist/layout/slotHostPool.svelte.d.ts +2 -1
- package/dist/layout/slotHostPool.svelte.js +6 -3
- package/dist/layout/slotHostPool.test.js +17 -0
- package/dist/layout/store.projectScope.test.js +76 -0
- package/dist/layout/store.svelte.d.ts +6 -0
- package/dist/layout/store.svelte.js +43 -13
- package/dist/layout/tree-walk.d.ts +8 -1
- package/dist/layout/tree-walk.js +11 -1
- package/dist/layout/tree-walk.test.js +53 -1
- package/dist/layout/types.d.ts +27 -0
- package/dist/layout/types.test.js +28 -0
- package/dist/layouts-shard/LayoutsSection.svelte +1 -1
- package/dist/layouts-shard/layoutsShard.svelte.js +2 -5
- package/dist/layouts-shard/layoutsShard.svelte.test.js +2 -2
- package/dist/overlays/FloatFrame.svelte +4 -1
- package/dist/overlays/float.d.ts +7 -1
- package/dist/overlays/float.js +4 -0
- package/dist/projects-shard/ProjectsSection.svelte +1 -5
- package/dist/projects-shard/projectsShard.svelte.js +1 -5
- package/dist/registry/installer.js +1 -1
- package/dist/registry/loader.d.ts +1 -1
- package/dist/registry/loader.js +3 -3
- package/dist/registry/permission-descriptions.test.js +2 -2
- package/dist/registry/register.js +1 -1
- package/dist/registry/register.test.js +1 -1
- package/dist/runtime/runVerb-shell.test.js +1 -1
- package/dist/runtime/runVerb.js +2 -2
- package/dist/runtime/runVerb.test.js +9 -9
- package/dist/sh3Api/headless.js +1 -1
- package/dist/sh3core-shard/sh3coreShard.svelte.js +1 -6
- package/dist/shards/ctx-fetch.test.js +9 -9
- package/dist/shards/lifecycle.svelte.d.ts +108 -0
- package/dist/shards/lifecycle.svelte.js +551 -0
- package/dist/shards/lifecycle.test.js +139 -0
- package/dist/shards/types.d.ts +56 -22
- package/dist/shell-shard/shellShard.svelte.js +11 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/shards/activate-browse.test.js +0 -120
- package/dist/shards/activate-contributions.test.js +0 -141
- package/dist/shards/activate-error-isolation.test.js +0 -98
- package/dist/shards/activate-fields.svelte.test.d.ts +0 -1
- package/dist/shards/activate-fields.svelte.test.js +0 -121
- package/dist/shards/activate-on-key-revoked.test.d.ts +0 -1
- package/dist/shards/activate-on-key-revoked.test.js +0 -60
- package/dist/shards/activate-runtime.test.d.ts +0 -1
- package/dist/shards/activate-runtime.test.js +0 -299
- package/dist/shards/activate-scopeid.test.d.ts +0 -1
- package/dist/shards/activate-scopeid.test.js +0 -21
- package/dist/shards/activate.svelte.d.ts +0 -102
- package/dist/shards/activate.svelte.js +0 -403
- /package/dist/{shards/activate-browse.test.d.ts → actions/dispatcher-v3.test.d.ts} +0 -0
- /package/dist/{shards/activate-contributions.test.d.ts → layout/store.projectScope.test.d.ts} +0 -0
- /package/dist/shards/{activate-error-isolation.test.d.ts → lifecycle.test.d.ts} +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { getShardBinding, rotateShardDocumentNamespace, __resetLifecycleForTest, registerAllShards, runAppActivate, runAppDeactivate, rebuildShardEntry, shardEntries, } from './lifecycle.svelte';
|
|
3
|
+
import { registerShard, __resetShardRegistryForTest, erroredShards } from './lifecycle.svelte';
|
|
4
|
+
describe('shards/lifecycle — binding map', () => {
|
|
5
|
+
beforeEach(() => __resetLifecycleForTest());
|
|
6
|
+
it('returns null for an unbound shard', () => {
|
|
7
|
+
expect(getShardBinding('foo')).toBeNull();
|
|
8
|
+
});
|
|
9
|
+
it('records and reads back a binding', () => {
|
|
10
|
+
rotateShardDocumentNamespace('foo', 'myApp');
|
|
11
|
+
expect(getShardBinding('foo')).toBe('myApp');
|
|
12
|
+
});
|
|
13
|
+
it('clears a binding when passed null', () => {
|
|
14
|
+
rotateShardDocumentNamespace('foo', 'myApp');
|
|
15
|
+
rotateShardDocumentNamespace('foo', null);
|
|
16
|
+
expect(getShardBinding('foo')).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
it('overwrites an existing binding', () => {
|
|
19
|
+
rotateShardDocumentNamespace('foo', 'app1');
|
|
20
|
+
rotateShardDocumentNamespace('foo', 'app2');
|
|
21
|
+
expect(getShardBinding('foo')).toBe('app2');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('registerAllShards', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
__resetLifecycleForTest();
|
|
27
|
+
__resetShardRegistryForTest();
|
|
28
|
+
});
|
|
29
|
+
it('runs register(ctx) on every registered shard', async () => {
|
|
30
|
+
const registered = [];
|
|
31
|
+
const shardA = {
|
|
32
|
+
manifest: { id: 'reg-a', label: 'A', version: '0.0.0', views: [] },
|
|
33
|
+
register(_ctx) { registered.push('reg-a'); },
|
|
34
|
+
};
|
|
35
|
+
const shardB = {
|
|
36
|
+
manifest: { id: 'reg-b', label: 'B', version: '0.0.0', views: [] },
|
|
37
|
+
register(_ctx) { registered.push('reg-b'); },
|
|
38
|
+
};
|
|
39
|
+
registerShard(shardA);
|
|
40
|
+
registerShard(shardB);
|
|
41
|
+
await registerAllShards();
|
|
42
|
+
expect(registered.sort()).toEqual(['reg-a', 'reg-b']);
|
|
43
|
+
expect(shardEntries.has('reg-a')).toBe(true);
|
|
44
|
+
expect(shardEntries.has('reg-b')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it('verifies every manifest view received a factory', async () => {
|
|
47
|
+
var _a;
|
|
48
|
+
const shardWithMissingFactory = {
|
|
49
|
+
manifest: { id: 'miss-x', label: 'X', version: '0.0.0', views: [{ id: 'miss-x:view', label: 'V' }] },
|
|
50
|
+
register(_ctx) { },
|
|
51
|
+
};
|
|
52
|
+
registerShard(shardWithMissingFactory);
|
|
53
|
+
await expect(registerAllShards()).resolves.toBeUndefined();
|
|
54
|
+
expect((_a = erroredShards.get('miss-x')) === null || _a === void 0 ? void 0 : _a.error).toBeInstanceOf(Error);
|
|
55
|
+
});
|
|
56
|
+
it('continues registering remaining shards when one throws', async () => {
|
|
57
|
+
const calls = [];
|
|
58
|
+
const shardBad = {
|
|
59
|
+
manifest: { id: 'bad-reg', label: 'Bad', version: '0.0.0', views: [] },
|
|
60
|
+
register(_ctx) { calls.push('bad-reg'); throw new Error('boom'); },
|
|
61
|
+
};
|
|
62
|
+
const shardGood = {
|
|
63
|
+
manifest: { id: 'good-reg', label: 'Good', version: '0.0.0', views: [] },
|
|
64
|
+
register(_ctx) { calls.push('good-reg'); },
|
|
65
|
+
};
|
|
66
|
+
registerShard(shardBad);
|
|
67
|
+
registerShard(shardGood);
|
|
68
|
+
await registerAllShards();
|
|
69
|
+
expect(calls).toContain('good-reg');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('runAppActivate / runAppDeactivate', () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
__resetLifecycleForTest();
|
|
75
|
+
__resetShardRegistryForTest();
|
|
76
|
+
});
|
|
77
|
+
it('rotates document namespace and calls onAppActivate', async () => {
|
|
78
|
+
const calls = [];
|
|
79
|
+
const shard = {
|
|
80
|
+
manifest: { id: 'rotation-s', label: 'S', version: '0.0.0', views: [] },
|
|
81
|
+
register(_ctx) { },
|
|
82
|
+
onAppActivate(_ctx, appId) {
|
|
83
|
+
calls.push({ hook: 'activate', appId });
|
|
84
|
+
},
|
|
85
|
+
onAppDeactivate(_ctx, appId) {
|
|
86
|
+
calls.push({ hook: 'deactivate', appId });
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
registerShard(shard);
|
|
90
|
+
await registerAllShards();
|
|
91
|
+
await runAppActivate('rotation-s', 'myApp');
|
|
92
|
+
expect(getShardBinding('rotation-s')).toBe('myApp');
|
|
93
|
+
expect(calls).toContainEqual({ hook: 'activate', appId: 'myApp' });
|
|
94
|
+
await runAppDeactivate('rotation-s', 'myApp');
|
|
95
|
+
expect(getShardBinding('rotation-s')).toBeNull();
|
|
96
|
+
expect(calls).toContainEqual({ hook: 'deactivate', appId: 'myApp' });
|
|
97
|
+
});
|
|
98
|
+
it('auto-disposes contributions registered inside onAppActivate', async () => {
|
|
99
|
+
const shard = {
|
|
100
|
+
manifest: { id: 'bag-s', label: 'S', version: '0.0.0', views: [] },
|
|
101
|
+
register(_ctx) { },
|
|
102
|
+
onAppActivate(ctxParam) {
|
|
103
|
+
const ctx = ctxParam;
|
|
104
|
+
ctx.actions.register({ id: 'bag-s.action', label: 'A', scope: ['app'], run() { } });
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
registerShard(shard);
|
|
108
|
+
await registerAllShards();
|
|
109
|
+
await runAppActivate('bag-s', 'myApp');
|
|
110
|
+
const { listActions } = await import('../actions/registry');
|
|
111
|
+
expect(listActions().find(e => e.action.id === 'bag-s.action')).toBeDefined();
|
|
112
|
+
await runAppDeactivate('bag-s', 'myApp');
|
|
113
|
+
expect(listActions().find(e => e.action.id === 'bag-s.action')).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('hot-swap on re-register', () => {
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
__resetLifecycleForTest();
|
|
119
|
+
__resetShardRegistryForTest();
|
|
120
|
+
});
|
|
121
|
+
it('tears down old entry and rebuilds when shard re-registers', async () => {
|
|
122
|
+
var _a, _b, _c;
|
|
123
|
+
const original = {
|
|
124
|
+
manifest: { id: 'hot-s', label: 'Original', version: '0.0.0', views: [] },
|
|
125
|
+
register(_ctx) { },
|
|
126
|
+
};
|
|
127
|
+
registerShard(original);
|
|
128
|
+
await registerAllShards();
|
|
129
|
+
expect((_a = shardEntries.get('hot-s')) === null || _a === void 0 ? void 0 : _a.shard.manifest.label).toBe('Original');
|
|
130
|
+
const updated = {
|
|
131
|
+
manifest: { id: 'hot-s', label: 'Updated', version: '0.0.1', views: [] },
|
|
132
|
+
register(_ctx) { },
|
|
133
|
+
};
|
|
134
|
+
registerShard(updated);
|
|
135
|
+
await rebuildShardEntry('hot-s');
|
|
136
|
+
expect((_b = shardEntries.get('hot-s')) === null || _b === void 0 ? void 0 : _b.shard.manifest.label).toBe('Updated');
|
|
137
|
+
expect((_c = shardEntries.get('hot-s')) === null || _c === void 0 ? void 0 : _c.shard.manifest.version).toBe('0.0.1');
|
|
138
|
+
});
|
|
139
|
+
});
|
package/dist/shards/types.d.ts
CHANGED
|
@@ -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
|
|
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';
|
|
@@ -9,7 +9,7 @@ import type { Sh3Api } from '../verbs/types';
|
|
|
9
9
|
import type { ShardContextKeys } from '../keys/types';
|
|
10
10
|
import type { ContributionsApi } from '../contributions/types';
|
|
11
11
|
import type { ActionsApi } from '../actions/types';
|
|
12
|
-
import type { TreeRootRef, SlotRole } from '../layout/types';
|
|
12
|
+
import type { TreeRootRef, SlotRole, JsonValue, RestoredSlot } from '../layout/types';
|
|
13
13
|
export { PERMISSION_KEYS_MINT, type ShardContextKeys, type ApiKeyPublic, type MintOpts, ScopeEscalationError, ConsentDeniedError } from '../keys/types';
|
|
14
14
|
/**
|
|
15
15
|
* The object returned by `ViewFactory.mount`. The framework calls
|
|
@@ -65,6 +65,13 @@ export interface MountContext {
|
|
|
65
65
|
* Not persisted with the layout — ephemeral per mount.
|
|
66
66
|
*/
|
|
67
67
|
meta?: Record<string, unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* Persistent view-level parameters passed by the caller when opening
|
|
70
|
+
* this view. Serialized with the layout tree — present on every mount
|
|
71
|
+
* including layout restore. Undefined when the slot was opened without
|
|
72
|
+
* props. Values are JSON-safe (`JsonValue`).
|
|
73
|
+
*/
|
|
74
|
+
props?: Record<string, JsonValue>;
|
|
68
75
|
/**
|
|
69
76
|
* Push dirty-state to the tab strip. The framework renders a dirty
|
|
70
77
|
* indicator (filled dot) on the tab when true, clears it when false.
|
|
@@ -217,8 +224,13 @@ export interface ShardContext {
|
|
|
217
224
|
* @param verb - The verb definition (name, summary, run function).
|
|
218
225
|
*/
|
|
219
226
|
registerVerb(verb: Verb): void;
|
|
220
|
-
/**
|
|
221
|
-
|
|
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;
|
|
222
234
|
/**
|
|
223
235
|
* Cross-origin-safe HTTP helper. Resolves relative `/api/...` paths
|
|
224
236
|
* against the configured serverUrl. In Tauri, routes through
|
|
@@ -344,23 +356,14 @@ export interface Shard {
|
|
|
344
356
|
/** Static description of this shard's identity and declared contributions. */
|
|
345
357
|
manifest: ShardManifest;
|
|
346
358
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
* Optional self-starting hook. A shard that defines `autostart` is
|
|
354
|
-
* eagerly activated by the framework at boot (right after the register
|
|
355
|
-
* pass finishes) instead of waiting for an app to require it. `activate`
|
|
356
|
-
* runs first; `autostart` runs immediately after and may take imperative
|
|
357
|
-
* action — docking its own views into the active layout, opening a
|
|
358
|
-
* modal, subscribing to framework state, etc. The `__sh3core__` pseudo-
|
|
359
|
-
* shard uses this with a no-op body so its activation path is uniform
|
|
360
|
-
* with other self-starting shards. Diagnostic-style shards use it to
|
|
361
|
-
* do real work.
|
|
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()`.
|
|
362
365
|
*/
|
|
363
|
-
|
|
366
|
+
register(ctx: ShardContext): void | Promise<void>;
|
|
364
367
|
/** Optional cleanup hook called when the shard is deactivated. Release timers, subscriptions, and external resources here. */
|
|
365
368
|
deactivate?(): void | Promise<void>;
|
|
366
369
|
/**
|
|
@@ -368,14 +371,45 @@ export interface Shard {
|
|
|
368
371
|
* remains active; its views and state are preserved. Return `false`
|
|
369
372
|
* (sync or async) to cancel the navigation.
|
|
370
373
|
*/
|
|
371
|
-
suspend?(): void | false | Promise<void | false>;
|
|
374
|
+
suspend?(ctx: ShardContext): void | false | Promise<void | false>;
|
|
372
375
|
/**
|
|
373
376
|
* Called when the owning app resumes from Home. Receives the same
|
|
374
377
|
* `ShardContext` that `activate` received.
|
|
375
378
|
*/
|
|
376
379
|
resume?(ctx: ShardContext): void | Promise<void>;
|
|
377
380
|
/** Fires when a key minted by this shard is revoked from any source. */
|
|
378
|
-
onKeyRevoked?(id: string): void | Promise<void>;
|
|
381
|
+
onKeyRevoked?(ctx: ShardContext, id: string): void | Promise<void>;
|
|
382
|
+
/**
|
|
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`.
|
|
389
|
+
*/
|
|
390
|
+
onAppActivate?(ctx: ShardContext, appId: string): void | Promise<void>;
|
|
391
|
+
/**
|
|
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.
|
|
395
|
+
*/
|
|
396
|
+
onAppDeactivate?(ctx: ShardContext, appId: string): void | Promise<void>;
|
|
397
|
+
/**
|
|
398
|
+
* Called BEFORE layout slots begin mounting, after all required shards have
|
|
399
|
+
* activated. `slots` contains every slot in the restored layout tree that
|
|
400
|
+
* has a non-null viewId, including their persisted `props`. Register any
|
|
401
|
+
* slot-specific contributions here (e.g. `EDITOR_DOCUMENT_POINT` keyed by
|
|
402
|
+
* `slotId`) so they are in place when the view factory's `mount()` is called.
|
|
403
|
+
*/
|
|
404
|
+
onLayoutWillRestore?(ctx: ShardContext, slots: RestoredSlot[]): void | Promise<void>;
|
|
405
|
+
/**
|
|
406
|
+
* Called AFTER the layout has been switched to the app's tree and slots
|
|
407
|
+
* have begun mounting. Use for post-mount wiring and reconciliation.
|
|
408
|
+
* Note: view factories may still be mounting asynchronously (microtask);
|
|
409
|
+
* this hook fires after the layout render, not after all `mount()` calls
|
|
410
|
+
* have returned.
|
|
411
|
+
*/
|
|
412
|
+
onLayoutRestored?(ctx: ShardContext, slots: RestoredSlot[]): void;
|
|
379
413
|
}
|
|
380
414
|
/**
|
|
381
415
|
* Source-level shape of a shard as written by external package authors.
|
|
@@ -25,10 +25,11 @@ import { focusView } from '../layout/inspection';
|
|
|
25
25
|
import { floatManager } from '../overlays/float';
|
|
26
26
|
import { getUser, isAdmin } from '../auth/index';
|
|
27
27
|
import { __bindZone, __unbindZone } from './buffer-zone-state.svelte';
|
|
28
|
+
import { getAuthToken } from '../transport/authToken';
|
|
28
29
|
export { makeSh3ApiHeadless, makeSh3ApiForTest } from '../sh3Api/headless';
|
|
29
30
|
export const shellShard = {
|
|
30
31
|
manifest,
|
|
31
|
-
|
|
32
|
+
register(ctx) {
|
|
32
33
|
registerV1Verbs(ctx);
|
|
33
34
|
const shell = makeSh3ApiHeadless(ctx.zones);
|
|
34
35
|
// Bind the shell-shard's workspace zone — backs scrollback persistence
|
|
@@ -58,7 +59,15 @@ export const shellShard = {
|
|
|
58
59
|
var _a;
|
|
59
60
|
const proto = typeof location !== 'undefined' && location.protocol === 'https:' ? 'wss' : 'ws';
|
|
60
61
|
const host = typeof location !== 'undefined' ? location.host : 'localhost';
|
|
61
|
-
|
|
62
|
+
let wsUrl = `${proto}://${host}/api/shell/session`;
|
|
63
|
+
// Tauri WebSocket can't set Authorization headers; use the query-param
|
|
64
|
+
// token fallback already supported by extractSessionToken() in auth.ts.
|
|
65
|
+
const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window);
|
|
66
|
+
if (isTauri) {
|
|
67
|
+
const tok = getAuthToken();
|
|
68
|
+
if (tok)
|
|
69
|
+
wsUrl += `?token=${encodeURIComponent(tok)}`;
|
|
70
|
+
}
|
|
62
71
|
const user = getUser();
|
|
63
72
|
const userId = (_a = user === null || user === void 0 ? void 0 : user.id) !== null && _a !== void 0 ? _a : 'guest';
|
|
64
73
|
const role = isAdmin() ? 'admin' : 'user';
|
|
@@ -76,9 +85,6 @@ export const shellShard = {
|
|
|
76
85
|
};
|
|
77
86
|
ctx.registerView('shell:terminal', factory);
|
|
78
87
|
},
|
|
79
|
-
autostart() {
|
|
80
|
-
// Intentionally empty — same pattern as __sh3core__.
|
|
81
|
-
},
|
|
82
88
|
deactivate() {
|
|
83
89
|
__unbindZone();
|
|
84
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.
|
|
2
|
+
export declare const VERSION = "0.22.1";
|
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.
|
|
2
|
+
export const VERSION = '0.22.1';
|
package/package.json
CHANGED
|
@@ -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
|
-
});
|