sh3-core 0.6.0
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/README.md +9 -0
- package/dist/Shell.svelte +283 -0
- package/dist/Shell.svelte.d.ts +5 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +50 -0
- package/dist/app/admin/ApiKeysView.svelte +169 -0
- package/dist/app/admin/ApiKeysView.svelte.d.ts +3 -0
- package/dist/app/admin/AuthSettingsView.svelte +105 -0
- package/dist/app/admin/AuthSettingsView.svelte.d.ts +3 -0
- package/dist/app/admin/SystemView.svelte +73 -0
- package/dist/app/admin/SystemView.svelte.d.ts +3 -0
- package/dist/app/admin/UsersView.svelte +188 -0
- package/dist/app/admin/UsersView.svelte.d.ts +3 -0
- package/dist/app/admin/adminApp.d.ts +7 -0
- package/dist/app/admin/adminApp.js +25 -0
- package/dist/app/admin/adminShard.svelte.d.ts +4 -0
- package/dist/app/admin/adminShard.svelte.js +62 -0
- package/dist/app/store/InstalledView.svelte +246 -0
- package/dist/app/store/InstalledView.svelte.d.ts +3 -0
- package/dist/app/store/StoreView.svelte +522 -0
- package/dist/app/store/StoreView.svelte.d.ts +3 -0
- package/dist/app/store/storeApp.d.ts +10 -0
- package/dist/app/store/storeApp.js +26 -0
- package/dist/app/store/storeShard.svelte.d.ts +38 -0
- package/dist/app/store/storeShard.svelte.js +218 -0
- package/dist/apps/lifecycle.d.ts +42 -0
- package/dist/apps/lifecycle.js +184 -0
- package/dist/apps/registry.svelte.d.ts +40 -0
- package/dist/apps/registry.svelte.js +59 -0
- package/dist/apps/terminal/manifest.d.ts +8 -0
- package/dist/apps/terminal/manifest.js +13 -0
- package/dist/apps/terminal/terminal-app.d.ts +7 -0
- package/dist/apps/terminal/terminal-app.js +14 -0
- package/dist/apps/types.d.ts +93 -0
- package/dist/apps/types.js +10 -0
- package/dist/artifact.d.ts +32 -0
- package/dist/artifact.js +1 -0
- package/dist/assets/SH3.png +0 -0
- package/dist/assets/icons.svg +1126 -0
- package/dist/assets.d.ts +13 -0
- package/dist/auth/GuestBanner.svelte +134 -0
- package/dist/auth/GuestBanner.svelte.d.ts +3 -0
- package/dist/auth/SignInWall.svelte +203 -0
- package/dist/auth/SignInWall.svelte.d.ts +7 -0
- package/dist/auth/auth.svelte.d.ts +69 -0
- package/dist/auth/auth.svelte.js +165 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +41 -0
- package/dist/auth/types.js +6 -0
- package/dist/build.d.ts +49 -0
- package/dist/build.js +236 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/createShell.d.ts +24 -0
- package/dist/createShell.js +131 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/env/client.d.ts +44 -0
- package/dist/env/client.js +106 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +1 -0
- package/dist/env/types.d.ts +12 -0
- package/dist/env/types.js +8 -0
- package/dist/host-entry.d.ts +13 -0
- package/dist/host-entry.js +17 -0
- package/dist/host.d.ts +15 -0
- package/dist/host.js +86 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +262 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +200 -0
- package/dist/layout/inspection.d.ts +72 -0
- package/dist/layout/inspection.js +209 -0
- package/dist/layout/ops.d.ts +100 -0
- package/dist/layout/ops.js +310 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +153 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/migrations/shell-rename.d.ts +16 -0
- package/dist/migrations/shell-rename.js +48 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/platform/index.d.ts +10 -0
- package/dist/platform/index.js +33 -0
- package/dist/platform/tauri-backend.d.ts +15 -0
- package/dist/platform/tauri-backend.js +58 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/primitives/base.css +42 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +117 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +168 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +185 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +262 -0
- package/dist/registry/types.js +14 -0
- package/dist/server-shard/types.d.ts +67 -0
- package/dist/server-shard/types.js +13 -0
- package/dist/sh3core-shard/ShellHome.svelte +192 -0
- package/dist/sh3core-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/sh3core-shard/ShellTitle.svelte +171 -0
- package/dist/sh3core-shard/ShellTitle.svelte.d.ts +3 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.d.ts +2 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +53 -0
- package/dist/shards/activate.svelte.d.ts +52 -0
- package/dist/shards/activate.svelte.js +186 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +207 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/InputLine.svelte +133 -0
- package/dist/shell-shard/InputLine.svelte.d.ts +11 -0
- package/dist/shell-shard/ScrollbackView.svelte +47 -0
- package/dist/shell-shard/ScrollbackView.svelte.d.ts +7 -0
- package/dist/shell-shard/Terminal.svelte +122 -0
- package/dist/shell-shard/Terminal.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/PromptEntry.svelte +25 -0
- package/dist/shell-shard/entries/PromptEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/RichEntry.svelte +19 -0
- package/dist/shell-shard/entries/RichEntry.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/StatusEntry.svelte +22 -0
- package/dist/shell-shard/entries/StatusEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/TextEntry.svelte +25 -0
- package/dist/shell-shard/entries/TextEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/manifest.d.ts +2 -0
- package/dist/shell-shard/manifest.js +11 -0
- package/dist/shell-shard/protocol.d.ts +90 -0
- package/dist/shell-shard/protocol.js +11 -0
- package/dist/shell-shard/registry.d.ts +69 -0
- package/dist/shell-shard/registry.js +47 -0
- package/dist/shell-shard/rich/AppCard.svelte +25 -0
- package/dist/shell-shard/rich/AppCard.svelte.d.ts +10 -0
- package/dist/shell-shard/rich/AppsTable.svelte +29 -0
- package/dist/shell-shard/rich/AppsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/EnvTable.svelte +27 -0
- package/dist/shell-shard/rich/EnvTable.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/HelpTable.svelte +29 -0
- package/dist/shell-shard/rich/HelpTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/HistoryList.svelte +37 -0
- package/dist/shell-shard/rich/HistoryList.svelte.d.ts +9 -0
- package/dist/shell-shard/rich/ShardsTable.svelte +28 -0
- package/dist/shell-shard/rich/ShardsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/ViewsTable.svelte +31 -0
- package/dist/shell-shard/rich/ViewsTable.svelte.d.ts +13 -0
- package/dist/shell-shard/rich/ZoneTree.svelte +19 -0
- package/dist/shell-shard/rich/ZoneTree.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/ZonesTable.svelte +27 -0
- package/dist/shell-shard/rich/ZonesTable.svelte.d.ts +11 -0
- package/dist/shell-shard/scrollback.svelte.d.ts +36 -0
- package/dist/shell-shard/scrollback.svelte.js +43 -0
- package/dist/shell-shard/session-client.svelte.d.ts +23 -0
- package/dist/shell-shard/session-client.svelte.js +120 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +139 -0
- package/dist/shell-shard/verbs/apps.d.ts +3 -0
- package/dist/shell-shard/verbs/apps.js +50 -0
- package/dist/shell-shard/verbs/clear.d.ts +2 -0
- package/dist/shell-shard/verbs/clear.js +7 -0
- package/dist/shell-shard/verbs/help.d.ts +2 -0
- package/dist/shell-shard/verbs/help.js +21 -0
- package/dist/shell-shard/verbs/history.d.ts +2 -0
- package/dist/shell-shard/verbs/history.js +20 -0
- package/dist/shell-shard/verbs/index.d.ts +2 -0
- package/dist/shell-shard/verbs/index.js +29 -0
- package/dist/shell-shard/verbs/session.d.ts +5 -0
- package/dist/shell-shard/verbs/session.js +65 -0
- package/dist/shell-shard/verbs/shards.d.ts +2 -0
- package/dist/shell-shard/verbs/shards.js +14 -0
- package/dist/shell-shard/verbs/views.d.ts +4 -0
- package/dist/shell-shard/verbs/views.js +90 -0
- package/dist/shell-shard/verbs/zones.d.ts +3 -0
- package/dist/shell-shard/verbs/zones.js +38 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/manage.d.ts +14 -0
- package/dist/state/manage.js +40 -0
- package/dist/state/types.d.ts +55 -0
- package/dist/state/types.js +17 -0
- package/dist/state/zones.svelte.d.ts +53 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/theme.d.ts +28 -0
- package/dist/theme.js +92 -0
- package/dist/tokens.css +102 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +60 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ZonesTable from '../rich/ZonesTable.svelte';
|
|
2
|
+
import ZoneTree from '../rich/ZoneTree.svelte';
|
|
3
|
+
export const zonesVerb = {
|
|
4
|
+
name: 'zones',
|
|
5
|
+
summary: 'List zones for the current user (optionally scoped to a shard).',
|
|
6
|
+
async run(ctx, args) {
|
|
7
|
+
const rows = ctx.shell.listZones(args[0]);
|
|
8
|
+
ctx.scrollback.push({
|
|
9
|
+
kind: 'rich',
|
|
10
|
+
component: ZonesTable,
|
|
11
|
+
props: { data: { rows } },
|
|
12
|
+
ts: Date.now(),
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export const zoneVerb = {
|
|
17
|
+
name: 'zone',
|
|
18
|
+
summary: 'Dump the contents of a zone as a collapsible JSON tree.',
|
|
19
|
+
async run(ctx, args) {
|
|
20
|
+
const [shardId, zoneName] = args;
|
|
21
|
+
if (!shardId || !zoneName) {
|
|
22
|
+
ctx.scrollback.push({
|
|
23
|
+
kind: 'status',
|
|
24
|
+
text: 'usage: zone <shardId> <zoneName>',
|
|
25
|
+
level: 'warn',
|
|
26
|
+
ts: Date.now(),
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const value = ctx.shell.readZone(shardId, zoneName);
|
|
31
|
+
ctx.scrollback.push({
|
|
32
|
+
kind: 'rich',
|
|
33
|
+
component: ZoneTree,
|
|
34
|
+
props: { data: { value } },
|
|
35
|
+
ts: Date.now(),
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { StateZones } from './state/zones.svelte';
|
|
2
|
+
import type { ZoneSchema } from './state/types';
|
|
3
|
+
import { type ModalManager } from './overlays/modal';
|
|
4
|
+
import { type PopupManager } from './overlays/popup';
|
|
5
|
+
import { type ToastManager } from './overlays/toast';
|
|
6
|
+
/**
|
|
7
|
+
* The process-wide shell singleton exposed to shards and the shell's own
|
|
8
|
+
* internal code. Provides state zone creation and overlay managers.
|
|
9
|
+
* Shards receive a pre-bound version via `ShardContext` rather than
|
|
10
|
+
* accessing `shell` directly.
|
|
11
|
+
*/
|
|
12
|
+
export interface Shell {
|
|
13
|
+
/**
|
|
14
|
+
* Declare the state zones a shard (or the shell itself) wants to use.
|
|
15
|
+
* Returns a live reactive object per declared zone. Persistent zones
|
|
16
|
+
* are hydrated from the backend before the call returns.
|
|
17
|
+
*/
|
|
18
|
+
state<T extends ZoneSchema>(shardId: string, schema: T): StateZones<T>;
|
|
19
|
+
/** Stackable, focus-trapped dialogs. See overlays/modal.ts. */
|
|
20
|
+
modal: ModalManager;
|
|
21
|
+
/** Anchored, single-item, outside-click-dismissable popups. */
|
|
22
|
+
popup: PopupManager;
|
|
23
|
+
/** Auto-dismissing notification toasts. */
|
|
24
|
+
toast: ToastManager;
|
|
25
|
+
}
|
|
26
|
+
/** The process-wide shell instance. Framework-internal code uses this directly; shards receive a scoped view via `ShardContext`. */
|
|
27
|
+
export declare const shell: Shell;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* shell — the framework-facing entry point passed to shards.
|
|
3
|
+
*
|
|
4
|
+
* Conceptually this is the object a shard receives from its activation
|
|
5
|
+
* callback; it exposes the APIs the shard is allowed to touch.
|
|
6
|
+
*
|
|
7
|
+
* Phase 3: state zones.
|
|
8
|
+
* Phase 4: registerView (lives on ShardContext, not here).
|
|
9
|
+
* Phase 5: modal / popup / toast overlay managers.
|
|
10
|
+
*
|
|
11
|
+
* For now `shell` is a module-level singleton. Once the shard lifecycle
|
|
12
|
+
* exists in a fuller form, per-shard shells will be constructed per
|
|
13
|
+
* activation with the shardId baked in, so shards no longer need to pass
|
|
14
|
+
* their own id to `state`. Overlay managers stay process-wide because
|
|
15
|
+
* the layer stack is a shell-global resource.
|
|
16
|
+
*/
|
|
17
|
+
import { createStateZones } from './state/zones.svelte';
|
|
18
|
+
import { modalManager } from './overlays/modal';
|
|
19
|
+
import { popupManager } from './overlays/popup';
|
|
20
|
+
import { toastManager } from './overlays/toast';
|
|
21
|
+
/** The process-wide shell instance. Framework-internal code uses this directly; shards receive a scoped view via `ShardContext`. */
|
|
22
|
+
export const shell = {
|
|
23
|
+
state: createStateZones,
|
|
24
|
+
modal: modalManager,
|
|
25
|
+
popup: popupManager,
|
|
26
|
+
toast: toastManager,
|
|
27
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Backend } from './types';
|
|
2
|
+
export declare class MemoryBackend implements Backend {
|
|
3
|
+
#private;
|
|
4
|
+
read(shardId: string): unknown | undefined;
|
|
5
|
+
write(shardId: string, value: unknown): void;
|
|
6
|
+
delete(shardId: string): void;
|
|
7
|
+
list(): string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Persists one JSON blob per shardId under a prefixed localStorage key.
|
|
11
|
+
*
|
|
12
|
+
* Keys are of the form `${prefix}${shardId}`. Each zone that uses this
|
|
13
|
+
* backend picks its own prefix so keys don't collide across zones (e.g.
|
|
14
|
+
* `sh3:workspace:` for the workspace zone, `sh3:user:` for the user zone).
|
|
15
|
+
*
|
|
16
|
+
* Values are JSON-serialized; non-serializable values are silently dropped
|
|
17
|
+
* by `JSON.stringify`. Callers are expected to keep zone contents plain.
|
|
18
|
+
*/
|
|
19
|
+
export declare class LocalStorageBackend implements Backend {
|
|
20
|
+
#private;
|
|
21
|
+
constructor(prefix: string);
|
|
22
|
+
read(shardId: string): unknown | undefined;
|
|
23
|
+
write(shardId: string, value: unknown): void;
|
|
24
|
+
delete(shardId: string): void;
|
|
25
|
+
list(): string[];
|
|
26
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Backend implementations for phase 3.
|
|
3
|
+
*
|
|
4
|
+
* MemoryBackend — ephemeral/session zones (and tests)
|
|
5
|
+
* LocalStorageBackend — workspace/user zones on web
|
|
6
|
+
*
|
|
7
|
+
* Tauri FS backends, IndexedDB, and remote sync are deferred per the
|
|
8
|
+
* roadmap. Any future backend need only conform to the `Backend` interface
|
|
9
|
+
* in ./types.ts for shard code to work against it unchanged.
|
|
10
|
+
*/
|
|
11
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
12
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
13
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
14
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
|
+
};
|
|
16
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
17
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
18
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
19
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
20
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
21
|
+
};
|
|
22
|
+
var _MemoryBackend_map, _LocalStorageBackend_prefix;
|
|
23
|
+
export class MemoryBackend {
|
|
24
|
+
constructor() {
|
|
25
|
+
_MemoryBackend_map.set(this, new Map());
|
|
26
|
+
}
|
|
27
|
+
read(shardId) {
|
|
28
|
+
return __classPrivateFieldGet(this, _MemoryBackend_map, "f").get(shardId);
|
|
29
|
+
}
|
|
30
|
+
write(shardId, value) {
|
|
31
|
+
__classPrivateFieldGet(this, _MemoryBackend_map, "f").set(shardId, value);
|
|
32
|
+
}
|
|
33
|
+
delete(shardId) {
|
|
34
|
+
__classPrivateFieldGet(this, _MemoryBackend_map, "f").delete(shardId);
|
|
35
|
+
}
|
|
36
|
+
list() {
|
|
37
|
+
return [...__classPrivateFieldGet(this, _MemoryBackend_map, "f").keys()];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
_MemoryBackend_map = new WeakMap();
|
|
41
|
+
/**
|
|
42
|
+
* Persists one JSON blob per shardId under a prefixed localStorage key.
|
|
43
|
+
*
|
|
44
|
+
* Keys are of the form `${prefix}${shardId}`. Each zone that uses this
|
|
45
|
+
* backend picks its own prefix so keys don't collide across zones (e.g.
|
|
46
|
+
* `sh3:workspace:` for the workspace zone, `sh3:user:` for the user zone).
|
|
47
|
+
*
|
|
48
|
+
* Values are JSON-serialized; non-serializable values are silently dropped
|
|
49
|
+
* by `JSON.stringify`. Callers are expected to keep zone contents plain.
|
|
50
|
+
*/
|
|
51
|
+
export class LocalStorageBackend {
|
|
52
|
+
constructor(prefix) {
|
|
53
|
+
_LocalStorageBackend_prefix.set(this, void 0);
|
|
54
|
+
__classPrivateFieldSet(this, _LocalStorageBackend_prefix, prefix, "f");
|
|
55
|
+
}
|
|
56
|
+
read(shardId) {
|
|
57
|
+
if (typeof localStorage === 'undefined')
|
|
58
|
+
return undefined;
|
|
59
|
+
const raw = localStorage.getItem(__classPrivateFieldGet(this, _LocalStorageBackend_prefix, "f") + shardId);
|
|
60
|
+
if (raw == null)
|
|
61
|
+
return undefined;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
catch (_a) {
|
|
66
|
+
// Corrupt entry — treat as missing rather than crashing the shard.
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
write(shardId, value) {
|
|
71
|
+
if (typeof localStorage === 'undefined')
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
localStorage.setItem(__classPrivateFieldGet(this, _LocalStorageBackend_prefix, "f") + shardId, JSON.stringify(value));
|
|
75
|
+
}
|
|
76
|
+
catch (_a) {
|
|
77
|
+
// Quota exceeded or serialization failure — swallow for phase 3;
|
|
78
|
+
// phase "post-prototype" will surface a proper error channel.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
delete(shardId) {
|
|
82
|
+
if (typeof localStorage === 'undefined')
|
|
83
|
+
return;
|
|
84
|
+
localStorage.removeItem(__classPrivateFieldGet(this, _LocalStorageBackend_prefix, "f") + shardId);
|
|
85
|
+
}
|
|
86
|
+
list() {
|
|
87
|
+
if (typeof localStorage === 'undefined')
|
|
88
|
+
return [];
|
|
89
|
+
const out = [];
|
|
90
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
91
|
+
const key = localStorage.key(i);
|
|
92
|
+
if (key && key.startsWith(__classPrivateFieldGet(this, _LocalStorageBackend_prefix, "f"))) {
|
|
93
|
+
out.push(key.slice(__classPrivateFieldGet(this, _LocalStorageBackend_prefix, "f").length));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
_LocalStorageBackend_prefix = new WeakMap();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ZoneName, ZoneManager } from './types';
|
|
2
|
+
/** Return all shard IDs that have data in the given zone. */
|
|
3
|
+
export declare function listZoneEntries(zone: ZoneName): string[];
|
|
4
|
+
/** Read a raw zone entry without creating a reactive proxy. */
|
|
5
|
+
export declare function peekZoneEntry(zone: ZoneName, shardId: string): unknown | undefined;
|
|
6
|
+
/** Delete one shard's data from a zone. */
|
|
7
|
+
export declare function clearZoneEntry(zone: ZoneName, shardId: string): void;
|
|
8
|
+
/** Delete all entries in a zone. Safe because `list()` returns a snapshot array. */
|
|
9
|
+
export declare function clearAllZoneEntries(zone: ZoneName): void;
|
|
10
|
+
/**
|
|
11
|
+
* Build a `ZoneManager` object. Called by context factories when the
|
|
12
|
+
* manifest declares the `state:manage` permission.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createZoneManager(): ZoneManager;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Zone management — public wrappers over the private backends for
|
|
3
|
+
* cross-shard state inspection and cleanup.
|
|
4
|
+
*
|
|
5
|
+
* These functions are not part of the shard-facing API directly.
|
|
6
|
+
* They are consumed by `createZoneManager()` which builds the
|
|
7
|
+
* `ZoneManager` object conditionally attached to contexts when
|
|
8
|
+
* the manifest declares the `state:manage` permission.
|
|
9
|
+
*/
|
|
10
|
+
import { backends } from './zones.svelte';
|
|
11
|
+
/** Return all shard IDs that have data in the given zone. */
|
|
12
|
+
export function listZoneEntries(zone) {
|
|
13
|
+
return backends[zone].list();
|
|
14
|
+
}
|
|
15
|
+
/** Read a raw zone entry without creating a reactive proxy. */
|
|
16
|
+
export function peekZoneEntry(zone, shardId) {
|
|
17
|
+
return backends[zone].read(shardId);
|
|
18
|
+
}
|
|
19
|
+
/** Delete one shard's data from a zone. */
|
|
20
|
+
export function clearZoneEntry(zone, shardId) {
|
|
21
|
+
backends[zone].delete(shardId);
|
|
22
|
+
}
|
|
23
|
+
/** Delete all entries in a zone. Safe because `list()` returns a snapshot array. */
|
|
24
|
+
export function clearAllZoneEntries(zone) {
|
|
25
|
+
for (const id of backends[zone].list()) {
|
|
26
|
+
backends[zone].delete(id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a `ZoneManager` object. Called by context factories when the
|
|
31
|
+
* manifest declares the `state:manage` permission.
|
|
32
|
+
*/
|
|
33
|
+
export function createZoneManager() {
|
|
34
|
+
return {
|
|
35
|
+
list: listZoneEntries,
|
|
36
|
+
peek: peekZoneEntry,
|
|
37
|
+
clear: clearZoneEntry,
|
|
38
|
+
clearAll: clearAllZoneEntries,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four state zones available to shards. Each zone has different lifetime
|
|
3
|
+
* and persistence semantics:
|
|
4
|
+
* - `ephemeral`: in-memory only; cleared when the shard is unloaded.
|
|
5
|
+
* - `session`: in-memory only; cleared when the app is closed.
|
|
6
|
+
* - `workspace`: persisted to localStorage; survives page reload.
|
|
7
|
+
* - `user`: persisted to localStorage; shared across all workspaces for this user.
|
|
8
|
+
*/
|
|
9
|
+
export type ZoneName = 'ephemeral' | 'session' | 'workspace' | 'user';
|
|
10
|
+
/** Zones whose contents are flushed to a persistent backend on change. */
|
|
11
|
+
export declare const PERSISTENT_ZONES: readonly ZoneName[];
|
|
12
|
+
/**
|
|
13
|
+
* A backend is a tiny KV store keyed by shardId. Each zone owns one backend.
|
|
14
|
+
* Values are arbitrary JSON-serializable objects — the backend is responsible
|
|
15
|
+
* for any (de)serialization needed for its medium.
|
|
16
|
+
*
|
|
17
|
+
* Future backends: IndexedDB, Tauri FS, remote sync — all conform to this
|
|
18
|
+
* interface so shard code is unaffected by deployment target.
|
|
19
|
+
*/
|
|
20
|
+
export interface Backend {
|
|
21
|
+
/** Read the stored value for a shard. Returns undefined if not present. */
|
|
22
|
+
read(shardId: string): unknown | undefined;
|
|
23
|
+
/** Write (create or overwrite) the stored value for a shard. */
|
|
24
|
+
write(shardId: string, value: unknown): void;
|
|
25
|
+
/** Delete the stored value for a shard. No-op if not present. */
|
|
26
|
+
delete(shardId: string): void;
|
|
27
|
+
/** Return all shard ids that have stored entries in this backend. */
|
|
28
|
+
list(): string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A zone schema is a record of zone name → initial state object. Any subset
|
|
32
|
+
* of zones may be declared; unused zones are simply omitted. Values inside
|
|
33
|
+
* each zone object become the defaults used when the backend has no prior
|
|
34
|
+
* entry for this shard.
|
|
35
|
+
*/
|
|
36
|
+
export type ZoneSchema = {
|
|
37
|
+
[K in ZoneName]?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Cross-shard zone management API. Allows enumeration, inspection, and
|
|
41
|
+
* cleanup of zone data across all shards. Only available on contexts
|
|
42
|
+
* whose manifest declares the `state:manage` permission.
|
|
43
|
+
*/
|
|
44
|
+
export interface ZoneManager {
|
|
45
|
+
/** Return all shard IDs that have data in the given zone. */
|
|
46
|
+
list(zone: ZoneName): string[];
|
|
47
|
+
/** Read a raw zone entry without creating a reactive proxy. Returns undefined if absent. */
|
|
48
|
+
peek(zone: ZoneName, shardId: string): unknown | undefined;
|
|
49
|
+
/** Delete one shard's data from a zone. */
|
|
50
|
+
clear(zone: ZoneName, shardId: string): void;
|
|
51
|
+
/** Delete all entries in a zone. */
|
|
52
|
+
clearAll(zone: ZoneName): void;
|
|
53
|
+
}
|
|
54
|
+
/** Permission string for cross-shard zone management access. */
|
|
55
|
+
export declare const PERMISSION_STATE_MANAGE = "state:manage";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* State zone types — framework plumbing for docs/design/state-zones.md.
|
|
3
|
+
*
|
|
4
|
+
* Phase 3 implements four of the six zones:
|
|
5
|
+
* ephemeral — in-memory, cleared on shard unload
|
|
6
|
+
* session — in-memory, cleared on app close
|
|
7
|
+
* workspace — persisted (localStorage KV for web)
|
|
8
|
+
* user — persisted (localStorage KV for web)
|
|
9
|
+
*
|
|
10
|
+
* Deferred until later phases:
|
|
11
|
+
* document — file-on-disk (phase "post-prototype")
|
|
12
|
+
* shared — lives on the bus, not storage (not a zone in the same sense)
|
|
13
|
+
*/
|
|
14
|
+
/** Zones whose contents are flushed to a persistent backend on change. */
|
|
15
|
+
export const PERSISTENT_ZONES = ['workspace', 'user'];
|
|
16
|
+
/** Permission string for cross-shard zone management access. */
|
|
17
|
+
export const PERMISSION_STATE_MANAGE = 'state:manage';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Backend, ZoneName, ZoneSchema } from './types';
|
|
2
|
+
export declare const backends: Record<ZoneName, Backend>;
|
|
3
|
+
/**
|
|
4
|
+
* Live reactive state object returned by `createStateZones`. Each key
|
|
5
|
+
* mirrors a zone declared in the schema; the value is a deeply-reactive
|
|
6
|
+
* proxy of the same shape. Undeclared zones are absent from the result.
|
|
7
|
+
*
|
|
8
|
+
* @typeParam T - The `ZoneSchema` used to declare zones and their defaults.
|
|
9
|
+
*/
|
|
10
|
+
export type StateZones<T extends ZoneSchema> = {
|
|
11
|
+
[K in keyof T]: T[K];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Create live reactive state zones for a shard (or the shell itself).
|
|
15
|
+
*
|
|
16
|
+
* Each zone declared in `schema` is backed by the appropriate store:
|
|
17
|
+
* `ephemeral` and `session` are in-memory; `workspace` and `user` are
|
|
18
|
+
* persisted to localStorage and hydrated before this call returns.
|
|
19
|
+
*
|
|
20
|
+
* Writes to persistent zones are debounced and coalesced per microtask.
|
|
21
|
+
* Zones not declared in `schema` are not created; the result only
|
|
22
|
+
* contains the keys the caller asked for.
|
|
23
|
+
*
|
|
24
|
+
* @param shardId - Unique shard identifier used as the storage namespace.
|
|
25
|
+
* @param schema - Record of zone names to their default values.
|
|
26
|
+
* @returns A reactive `StateZones<T>` object keyed to the declared zones.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createStateZones<T extends ZoneSchema>(shardId: string, schema: T): StateZones<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Swap a backend implementation. Intended for tests and for phase 4+ when
|
|
31
|
+
* Tauri or IndexedDB backends replace the defaults. Not part of the shard-
|
|
32
|
+
* facing API.
|
|
33
|
+
*/
|
|
34
|
+
export declare function __setBackend(zone: ZoneName, backend: Backend): void;
|
|
35
|
+
/**
|
|
36
|
+
* Read a raw persisted zone entry without creating a reactive proxy.
|
|
37
|
+
*
|
|
38
|
+
* Intended for framework-internal consumers (e.g. the shell's layout
|
|
39
|
+
* persistence) that need to inspect a stored value before deciding
|
|
40
|
+
* whether to pass it to `createStateZones`. Not part of the shard-facing
|
|
41
|
+
* API — shards always go through `createStateZones` so hydration is
|
|
42
|
+
* reactive and debounced-flush applies.
|
|
43
|
+
*
|
|
44
|
+
* Returns `undefined` for missing, corrupt, or memory-only-zone entries
|
|
45
|
+
* that have no stored value.
|
|
46
|
+
*/
|
|
47
|
+
export declare function peekZone(zone: ZoneName, shardId: string): unknown | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Delete a persisted zone entry. Intended for framework-internal use
|
|
50
|
+
* when a stored value is known to be incompatible (version mismatch,
|
|
51
|
+
* corruption) and should be removed before `createStateZones` hydrates.
|
|
52
|
+
*/
|
|
53
|
+
export declare function clearZone(zone: ZoneName, shardId: string): void;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* createStateZones — turns a declarative zone schema into live reactive
|
|
3
|
+
* per-zone objects backed by the right store for each zone.
|
|
4
|
+
*
|
|
5
|
+
* Usage (from a shard, or the shell itself):
|
|
6
|
+
*
|
|
7
|
+
* const state = createStateZones('graphlive', {
|
|
8
|
+
* ephemeral: { hoveredId: null as string | null },
|
|
9
|
+
* session: { lastFocus: null as string | null },
|
|
10
|
+
* workspace: { collapsed: false, panelSizes: [0.3, 0.7] },
|
|
11
|
+
* user: { showIcons: true },
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* state.workspace.collapsed = true; // persists to localStorage
|
|
15
|
+
* state.ephemeral.hoveredId = 'foo'; // in-memory only
|
|
16
|
+
*
|
|
17
|
+
* Guarantees (phase 3 subset of docs/design/state-zones.md):
|
|
18
|
+
* - Hydration before return: persistent zones are populated from the
|
|
19
|
+
* backend before this function returns, so reads in the next line of
|
|
20
|
+
* user code see persisted values.
|
|
21
|
+
* - Debounced flush: writes within the same microtask coalesce to a
|
|
22
|
+
* single backend write per (zone, shardId).
|
|
23
|
+
* - Atomicity per write: each flush serializes a full zone snapshot.
|
|
24
|
+
* - Isolation: zone data is keyed by shardId; shards cannot collide.
|
|
25
|
+
*
|
|
26
|
+
* Lifetime management (tracking teardown on shard deactivation) arrives in
|
|
27
|
+
* phase 4 when the shard contract exists. For phase 3, roots live until the
|
|
28
|
+
* page unloads, which is fine for a single-session shell.
|
|
29
|
+
*
|
|
30
|
+
* This file must be `.svelte.ts` so it can use the Svelte 5 runes
|
|
31
|
+
* `$state`, `$effect.root`, `$effect`, and `$state.snapshot` at module level.
|
|
32
|
+
*/
|
|
33
|
+
import { MemoryBackend, LocalStorageBackend } from './backends';
|
|
34
|
+
import { PERSISTENT_ZONES } from './types';
|
|
35
|
+
export const backends = {
|
|
36
|
+
ephemeral: new MemoryBackend(),
|
|
37
|
+
session: new MemoryBackend(),
|
|
38
|
+
workspace: new LocalStorageBackend('sh3:workspace:'),
|
|
39
|
+
user: new LocalStorageBackend('sh3:user:'),
|
|
40
|
+
};
|
|
41
|
+
const pending = new Map();
|
|
42
|
+
let flushScheduled = false;
|
|
43
|
+
function scheduleFlush(zone, shardId, value) {
|
|
44
|
+
pending.set(`${zone}:${shardId}`, { zone, shardId, value });
|
|
45
|
+
if (flushScheduled)
|
|
46
|
+
return;
|
|
47
|
+
flushScheduled = true;
|
|
48
|
+
queueMicrotask(() => {
|
|
49
|
+
flushScheduled = false;
|
|
50
|
+
for (const { zone, shardId, value } of pending.values()) {
|
|
51
|
+
backends[zone].write(shardId, value);
|
|
52
|
+
}
|
|
53
|
+
pending.clear();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create live reactive state zones for a shard (or the shell itself).
|
|
58
|
+
*
|
|
59
|
+
* Each zone declared in `schema` is backed by the appropriate store:
|
|
60
|
+
* `ephemeral` and `session` are in-memory; `workspace` and `user` are
|
|
61
|
+
* persisted to localStorage and hydrated before this call returns.
|
|
62
|
+
*
|
|
63
|
+
* Writes to persistent zones are debounced and coalesced per microtask.
|
|
64
|
+
* Zones not declared in `schema` are not created; the result only
|
|
65
|
+
* contains the keys the caller asked for.
|
|
66
|
+
*
|
|
67
|
+
* @param shardId - Unique shard identifier used as the storage namespace.
|
|
68
|
+
* @param schema - Record of zone names to their default values.
|
|
69
|
+
* @returns A reactive `StateZones<T>` object keyed to the declared zones.
|
|
70
|
+
*/
|
|
71
|
+
export function createStateZones(shardId, schema) {
|
|
72
|
+
const result = {};
|
|
73
|
+
for (const key of Object.keys(schema)) {
|
|
74
|
+
const defaults = schema[key];
|
|
75
|
+
const backend = backends[key];
|
|
76
|
+
// Hydrate: shallow-merge stored values over the schema defaults so new
|
|
77
|
+
// fields added to the schema in a later version get their defaults even
|
|
78
|
+
// for shards whose backend entries predate them.
|
|
79
|
+
const stored = backend.read(shardId);
|
|
80
|
+
const initial = Object.assign(Object.assign({}, defaults), (stored !== null && stored !== void 0 ? stored : {}));
|
|
81
|
+
// The reactive proxy. Deep reactivity via $state lets shards mutate
|
|
82
|
+
// nested fields (e.g. state.workspace.panelSizes[0] = 0.4) and still
|
|
83
|
+
// trigger the flush effect.
|
|
84
|
+
const proxy = $state(initial);
|
|
85
|
+
result[key] = proxy;
|
|
86
|
+
// Set up change tracking for persistent zones. Memory-only zones don't
|
|
87
|
+
// need it: nothing reads from them beyond the in-process proxy.
|
|
88
|
+
if (!PERSISTENT_ZONES.includes(key))
|
|
89
|
+
continue;
|
|
90
|
+
// $effect.root creates a standalone reactivity scope outside any
|
|
91
|
+
// component. The returned cleanup is intentionally not captured in
|
|
92
|
+
// phase 3 — see the "Lifetime management" note at the top of the file.
|
|
93
|
+
$effect.root(() => {
|
|
94
|
+
let first = true;
|
|
95
|
+
$effect(() => {
|
|
96
|
+
// $state.snapshot deeply reads every reactive field, which both
|
|
97
|
+
// establishes dependency tracking and produces a plain-object copy
|
|
98
|
+
// safe to hand to the backend.
|
|
99
|
+
const snap = $state.snapshot(proxy);
|
|
100
|
+
if (first) {
|
|
101
|
+
first = false;
|
|
102
|
+
return; // don't write back the just-hydrated value
|
|
103
|
+
}
|
|
104
|
+
scheduleFlush(key, shardId, snap);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
// ---------- test hooks ----------------------------------------------------
|
|
111
|
+
/**
|
|
112
|
+
* Swap a backend implementation. Intended for tests and for phase 4+ when
|
|
113
|
+
* Tauri or IndexedDB backends replace the defaults. Not part of the shard-
|
|
114
|
+
* facing API.
|
|
115
|
+
*/
|
|
116
|
+
export function __setBackend(zone, backend) {
|
|
117
|
+
backends[zone] = backend;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Read a raw persisted zone entry without creating a reactive proxy.
|
|
121
|
+
*
|
|
122
|
+
* Intended for framework-internal consumers (e.g. the shell's layout
|
|
123
|
+
* persistence) that need to inspect a stored value before deciding
|
|
124
|
+
* whether to pass it to `createStateZones`. Not part of the shard-facing
|
|
125
|
+
* API — shards always go through `createStateZones` so hydration is
|
|
126
|
+
* reactive and debounced-flush applies.
|
|
127
|
+
*
|
|
128
|
+
* Returns `undefined` for missing, corrupt, or memory-only-zone entries
|
|
129
|
+
* that have no stored value.
|
|
130
|
+
*/
|
|
131
|
+
export function peekZone(zone, shardId) {
|
|
132
|
+
return backends[zone].read(shardId);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Delete a persisted zone entry. Intended for framework-internal use
|
|
136
|
+
* when a stored value is known to be incompatible (version mismatch,
|
|
137
|
+
* corruption) and should be removed before `createStateZones` hydrates.
|
|
138
|
+
*/
|
|
139
|
+
export function clearZone(zone, shardId) {
|
|
140
|
+
backends[zone].delete(shardId);
|
|
141
|
+
}
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply CSS token overrides to :root and persist to User zone.
|
|
3
|
+
*
|
|
4
|
+
* Keys are token names without the `--` prefix (e.g. `'shell-accent'`).
|
|
5
|
+
* Values are any valid CSS value string.
|
|
6
|
+
*
|
|
7
|
+
* Calling this replaces ALL previous overrides — tokens not present in
|
|
8
|
+
* the new map are removed from :root.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setTokenOverrides(overrides: Record<string, string>): void;
|
|
11
|
+
/**
|
|
12
|
+
* Remove all token overrides from :root and clear persisted state.
|
|
13
|
+
* The shell reverts to the default tokens defined in tokens.css.
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearTokenOverrides(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Read the currently persisted token overrides.
|
|
18
|
+
* Returns an empty object if no overrides are stored.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getTokenOverrides(): Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Apply persisted token overrides to :root. Called once during shell boot
|
|
23
|
+
* before any component mounts, so the themed colors are visible from the
|
|
24
|
+
* first frame.
|
|
25
|
+
*
|
|
26
|
+
* This is framework-internal — not part of the shard-facing API.
|
|
27
|
+
*/
|
|
28
|
+
export declare function hydrateTokenOverrides(): void;
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Token override API — allows external packages (like sh3-style) to
|
|
3
|
+
* dynamically change shell CSS tokens and persist the selection to
|
|
4
|
+
* the User zone.
|
|
5
|
+
*
|
|
6
|
+
* The shell reads persisted overrides at boot (see createShell.ts) so
|
|
7
|
+
* themes survive page reloads. This module provides the write path.
|
|
8
|
+
*/
|
|
9
|
+
const STORAGE_KEY = 'sh3:user:__sh3core__:theme';
|
|
10
|
+
/** Keys currently set on :root by setTokenOverrides, tracked for clearTokenOverrides. */
|
|
11
|
+
let appliedKeys = [];
|
|
12
|
+
/**
|
|
13
|
+
* Apply CSS token overrides to :root and persist to User zone.
|
|
14
|
+
*
|
|
15
|
+
* Keys are token names without the `--` prefix (e.g. `'shell-accent'`).
|
|
16
|
+
* Values are any valid CSS value string.
|
|
17
|
+
*
|
|
18
|
+
* Calling this replaces ALL previous overrides — tokens not present in
|
|
19
|
+
* the new map are removed from :root.
|
|
20
|
+
*/
|
|
21
|
+
export function setTokenOverrides(overrides) {
|
|
22
|
+
// Remove previously applied keys that are not in the new set
|
|
23
|
+
const newKeys = Object.keys(overrides);
|
|
24
|
+
for (const key of appliedKeys) {
|
|
25
|
+
if (!newKeys.includes(key)) {
|
|
26
|
+
document.documentElement.style.removeProperty(`--${key}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Apply new overrides
|
|
30
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
31
|
+
document.documentElement.style.setProperty(`--${key}`, value);
|
|
32
|
+
}
|
|
33
|
+
appliedKeys = newKeys;
|
|
34
|
+
// Persist
|
|
35
|
+
try {
|
|
36
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(overrides));
|
|
37
|
+
}
|
|
38
|
+
catch (_a) {
|
|
39
|
+
// Storage full or unavailable — theme applies for this session only
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove all token overrides from :root and clear persisted state.
|
|
44
|
+
* The shell reverts to the default tokens defined in tokens.css.
|
|
45
|
+
*/
|
|
46
|
+
export function clearTokenOverrides() {
|
|
47
|
+
for (const key of appliedKeys) {
|
|
48
|
+
document.documentElement.style.removeProperty(`--${key}`);
|
|
49
|
+
}
|
|
50
|
+
appliedKeys = [];
|
|
51
|
+
try {
|
|
52
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
// Ignore
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read the currently persisted token overrides.
|
|
60
|
+
* Returns an empty object if no overrides are stored.
|
|
61
|
+
*/
|
|
62
|
+
export function getTokenOverrides() {
|
|
63
|
+
try {
|
|
64
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
65
|
+
if (!raw)
|
|
66
|
+
return {};
|
|
67
|
+
const parsed = JSON.parse(raw);
|
|
68
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
catch (_a) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Apply persisted token overrides to :root. Called once during shell boot
|
|
79
|
+
* before any component mounts, so the themed colors are visible from the
|
|
80
|
+
* first frame.
|
|
81
|
+
*
|
|
82
|
+
* This is framework-internal — not part of the shard-facing API.
|
|
83
|
+
*/
|
|
84
|
+
export function hydrateTokenOverrides() {
|
|
85
|
+
const overrides = getTokenOverrides();
|
|
86
|
+
if (Object.keys(overrides).length === 0)
|
|
87
|
+
return;
|
|
88
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
89
|
+
document.documentElement.style.setProperty(`--${key}`, value);
|
|
90
|
+
}
|
|
91
|
+
appliedKeys = Object.keys(overrides);
|
|
92
|
+
}
|