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,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* ToastItem — a single auto-dismissing notification.
|
|
4
|
+
*
|
|
5
|
+
* The toast manager mounts one ToastItem per notification into the
|
|
6
|
+
* layer-5 root. The item renders the message, applies level styling,
|
|
7
|
+
* and fades/slides in on mount. The manager (not the item) owns the
|
|
8
|
+
* auto-dismiss timer, so the item stays purely presentational.
|
|
9
|
+
*
|
|
10
|
+
* Dismiss-on-click is built into the item via the close prop so users
|
|
11
|
+
* can dismiss a toast early without waiting for its timer.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ToastLevel } from './types';
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
message,
|
|
18
|
+
level,
|
|
19
|
+
close,
|
|
20
|
+
}: {
|
|
21
|
+
message: string;
|
|
22
|
+
level: ToastLevel;
|
|
23
|
+
close: () => void;
|
|
24
|
+
} = $props();
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
28
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
29
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
30
|
+
<div
|
|
31
|
+
class="toast toast-{level}"
|
|
32
|
+
role="status"
|
|
33
|
+
aria-live="polite"
|
|
34
|
+
onclick={close}
|
|
35
|
+
>
|
|
36
|
+
<span class="toast-level">{level}</span>
|
|
37
|
+
<span class="toast-message">{message}</span>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<style>
|
|
41
|
+
.toast {
|
|
42
|
+
pointer-events: auto;
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: var(--shell-pad-md);
|
|
46
|
+
padding: var(--shell-pad-sm) var(--shell-pad-md);
|
|
47
|
+
background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
|
|
48
|
+
color: var(--shell-fg);
|
|
49
|
+
border: 1px solid var(--shell-border-strong);
|
|
50
|
+
border-left-width: 3px;
|
|
51
|
+
border-radius: var(--shell-radius-sm);
|
|
52
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
min-width: 220px;
|
|
55
|
+
max-width: 360px;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
animation: toast-in 160ms ease-out both;
|
|
58
|
+
}
|
|
59
|
+
.toast-level {
|
|
60
|
+
text-transform: uppercase;
|
|
61
|
+
font-family: var(--shell-font-mono);
|
|
62
|
+
font-size: 10px;
|
|
63
|
+
letter-spacing: 0.5px;
|
|
64
|
+
color: var(--shell-fg-muted);
|
|
65
|
+
}
|
|
66
|
+
.toast-message { flex: 1; }
|
|
67
|
+
|
|
68
|
+
.toast-info { border-left-color: var(--shell-accent); }
|
|
69
|
+
.toast-success { border-left-color: #5cb176; }
|
|
70
|
+
.toast-warn { border-left-color: #d6a84a; }
|
|
71
|
+
.toast-error { border-left-color: #d06060; }
|
|
72
|
+
|
|
73
|
+
@keyframes toast-in {
|
|
74
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
75
|
+
to { opacity: 1; transform: translateY(0); }
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToastLevel } from './types';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
message: string;
|
|
4
|
+
level: ToastLevel;
|
|
5
|
+
close: () => void;
|
|
6
|
+
};
|
|
7
|
+
declare const ToastItem: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type ToastItem = ReturnType<typeof ToastItem>;
|
|
9
|
+
export default ToastItem;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createFocusTrap(container: HTMLElement): () => void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* createFocusTrap — minimal Tab-cycling focus trap for modal frames.
|
|
3
|
+
*
|
|
4
|
+
* On install: remembers the currently focused element, moves focus to the
|
|
5
|
+
* first focusable descendant of `container`, and intercepts Tab/Shift+Tab
|
|
6
|
+
* to cycle within the container.
|
|
7
|
+
*
|
|
8
|
+
* On teardown (the returned disposer): removes the listener and restores
|
|
9
|
+
* focus to the previously active element if it's still connected to the DOM.
|
|
10
|
+
*
|
|
11
|
+
* Phase 5 scope is deliberately narrow: no aria-hidden on siblings, no
|
|
12
|
+
* Inert attribute management, no MutationObserver for dynamic content.
|
|
13
|
+
* Those refinements arrive when accessibility work lands post-prototype.
|
|
14
|
+
*/
|
|
15
|
+
const FOCUSABLE_SELECTOR = [
|
|
16
|
+
'a[href]',
|
|
17
|
+
'button:not([disabled])',
|
|
18
|
+
'input:not([disabled])',
|
|
19
|
+
'select:not([disabled])',
|
|
20
|
+
'textarea:not([disabled])',
|
|
21
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
22
|
+
].join(',');
|
|
23
|
+
export function createFocusTrap(container) {
|
|
24
|
+
const previouslyFocused = document.activeElement;
|
|
25
|
+
function getFocusables() {
|
|
26
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
27
|
+
}
|
|
28
|
+
function onKeydown(e) {
|
|
29
|
+
if (e.key !== 'Tab')
|
|
30
|
+
return;
|
|
31
|
+
const focusables = getFocusables();
|
|
32
|
+
if (focusables.length === 0) {
|
|
33
|
+
// Nothing to focus — swallow Tab so it can't escape the modal.
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const first = focusables[0];
|
|
38
|
+
const last = focusables[focusables.length - 1];
|
|
39
|
+
const active = document.activeElement;
|
|
40
|
+
if (e.shiftKey && (active === first || !container.contains(active))) {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
last.focus();
|
|
43
|
+
}
|
|
44
|
+
else if (!e.shiftKey && (active === last || !container.contains(active))) {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
first.focus();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
container.addEventListener('keydown', onKeydown);
|
|
50
|
+
// Defer initial focus to the next microtask so the container contents
|
|
51
|
+
// (which may still be rendering if createFocusTrap was called mid-mount)
|
|
52
|
+
// have a chance to appear in the DOM.
|
|
53
|
+
queueMicrotask(() => {
|
|
54
|
+
var _a;
|
|
55
|
+
const focusables = getFocusables();
|
|
56
|
+
((_a = focusables[0]) !== null && _a !== void 0 ? _a : container).focus();
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
container.removeEventListener('keydown', onKeydown);
|
|
60
|
+
if (previouslyFocused && document.contains(previouslyFocused)) {
|
|
61
|
+
previouslyFocused.focus();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
import type { ModalHandle, ModalOptions } from './types';
|
|
3
|
+
export interface ModalManager {
|
|
4
|
+
open<P extends Record<string, unknown>>(Content: Component<P & {
|
|
5
|
+
close: () => void;
|
|
6
|
+
}>, props?: P, options?: ModalOptions): ModalHandle;
|
|
7
|
+
closeAll(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const modalManager: ModalManager;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Modal manager — stackable, Escape-dismissed, focus-trapped dialogs.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* modalManager.open(Content, props?) → ModalHandle
|
|
6
|
+
* modalManager.closeAll()
|
|
7
|
+
*
|
|
8
|
+
* Semantics (from docs/design/layout.md):
|
|
9
|
+
* - Modals stack. Opening a second modal pushes it on top of the first.
|
|
10
|
+
* - Escape pops the topmost modal.
|
|
11
|
+
* - Backdrop click does NOT dismiss. Content owns its own close UI.
|
|
12
|
+
* - Each modal has its own focus trap (installed by ModalFrame).
|
|
13
|
+
*
|
|
14
|
+
* Implementation notes:
|
|
15
|
+
* - Each open() creates a per-modal host <div> under the layer-4 root
|
|
16
|
+
* and mounts a ModalFrame into it. The host isolates modals for clean
|
|
17
|
+
* unmount/remove.
|
|
18
|
+
* - A SINGLE shared backdrop element is inserted into the layer-4 root
|
|
19
|
+
* on the first open and removed on the last close. It is repositioned
|
|
20
|
+
* on every stack change to sit directly BENEATH the topmost modal host
|
|
21
|
+
* (and above all other modal hosts), so stacked modals read as dimmed
|
|
22
|
+
* and clearly non-interactive while the top modal renders clear. The
|
|
23
|
+
* opacity is a single fixed value — depth-scaled opacity (an earlier
|
|
24
|
+
* cut) is no longer meaningful because only the "layer beneath the
|
|
25
|
+
* top" is ever being dimmed. Per-modal backdrops were the phase-5
|
|
26
|
+
* first cut; they compounded (2 modals ≈ 0.75, 3 ≈ 0.88, …) and were
|
|
27
|
+
* both unbounded and expensive.
|
|
28
|
+
* - The manager owns a single document-level Escape listener, installed
|
|
29
|
+
* lazily on first open and removed when the stack empties. This is
|
|
30
|
+
* simpler than per-modal listeners and avoids "which listener wins"
|
|
31
|
+
* ordering puzzles when modals stack.
|
|
32
|
+
* - close() is idempotent: calling it after the modal has been removed
|
|
33
|
+
* is a no-op, which makes it safe for content components to call from
|
|
34
|
+
* both a close button and an "async action finished" callback.
|
|
35
|
+
*/
|
|
36
|
+
import { mount, unmount } from 'svelte';
|
|
37
|
+
import ModalFrame from './ModalFrame.svelte';
|
|
38
|
+
import { getLayerRoot } from './roots';
|
|
39
|
+
const stack = [];
|
|
40
|
+
let escapeInstalled = false;
|
|
41
|
+
let backdrop = null;
|
|
42
|
+
/** Single fixed backdrop opacity. The backdrop is repositioned beneath
|
|
43
|
+
* the topmost modal on every stack change, so underlying modals appear
|
|
44
|
+
* dimmed (visually communicating they're non-interactive) while the top
|
|
45
|
+
* modal sits above the dim. Depth-scaled opacity is therefore obsolete:
|
|
46
|
+
* only one "layer beneath the top" is ever being dimmed. */
|
|
47
|
+
const BACKDROP_OPACITY = 0.5;
|
|
48
|
+
function syncBackdrop() {
|
|
49
|
+
const root = getLayerRoot('modal');
|
|
50
|
+
if (stack.length === 0) {
|
|
51
|
+
if (backdrop) {
|
|
52
|
+
backdrop.remove();
|
|
53
|
+
backdrop = null;
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!backdrop) {
|
|
58
|
+
backdrop = document.createElement('div');
|
|
59
|
+
backdrop.className = 'sh3-modal-backdrop';
|
|
60
|
+
backdrop.style.position = 'absolute';
|
|
61
|
+
backdrop.style.inset = '0';
|
|
62
|
+
backdrop.style.pointerEvents = 'auto';
|
|
63
|
+
backdrop.style.transition = 'background-color 120ms ease';
|
|
64
|
+
backdrop.style.background = `rgba(0, 0, 0, ${BACKDROP_OPACITY})`;
|
|
65
|
+
}
|
|
66
|
+
// Place the backdrop directly before the topmost modal host so every
|
|
67
|
+
// modal beneath it is dimmed while the top modal renders clear above.
|
|
68
|
+
const topHost = stack[stack.length - 1].host;
|
|
69
|
+
root.insertBefore(backdrop, topHost);
|
|
70
|
+
}
|
|
71
|
+
function onDocumentKeydown(e) {
|
|
72
|
+
if (e.key !== 'Escape')
|
|
73
|
+
return;
|
|
74
|
+
if (stack.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
stack[stack.length - 1].handle.close();
|
|
79
|
+
}
|
|
80
|
+
function ensureEscapeListener() {
|
|
81
|
+
if (escapeInstalled)
|
|
82
|
+
return;
|
|
83
|
+
escapeInstalled = true;
|
|
84
|
+
document.addEventListener('keydown', onDocumentKeydown, true);
|
|
85
|
+
}
|
|
86
|
+
function removeEscapeListenerIfIdle() {
|
|
87
|
+
if (stack.length > 0)
|
|
88
|
+
return;
|
|
89
|
+
if (!escapeInstalled)
|
|
90
|
+
return;
|
|
91
|
+
escapeInstalled = false;
|
|
92
|
+
document.removeEventListener('keydown', onDocumentKeydown, true);
|
|
93
|
+
}
|
|
94
|
+
function removeEntry(entry) {
|
|
95
|
+
const idx = stack.indexOf(entry);
|
|
96
|
+
if (idx < 0)
|
|
97
|
+
return; // already closed — idempotent
|
|
98
|
+
stack.splice(idx, 1);
|
|
99
|
+
unmount(entry.frame);
|
|
100
|
+
entry.host.remove();
|
|
101
|
+
syncBackdrop();
|
|
102
|
+
removeEscapeListenerIfIdle();
|
|
103
|
+
}
|
|
104
|
+
function openModal(Content, props, options) {
|
|
105
|
+
const root = getLayerRoot('modal');
|
|
106
|
+
const host = document.createElement('div');
|
|
107
|
+
host.className = 'sh3-modal-host';
|
|
108
|
+
host.style.position = 'absolute';
|
|
109
|
+
host.style.inset = '0';
|
|
110
|
+
host.style.pointerEvents = 'auto';
|
|
111
|
+
root.appendChild(host);
|
|
112
|
+
const entry = {};
|
|
113
|
+
const handle = {
|
|
114
|
+
close: () => removeEntry(entry),
|
|
115
|
+
};
|
|
116
|
+
const frame = mount(ModalFrame, {
|
|
117
|
+
target: host,
|
|
118
|
+
props: {
|
|
119
|
+
Content: Content,
|
|
120
|
+
contentProps: (props !== null && props !== void 0 ? props : {}),
|
|
121
|
+
close: handle.close,
|
|
122
|
+
boxStyle: options === null || options === void 0 ? void 0 : options.boxStyle,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
entry.host = host;
|
|
126
|
+
entry.frame = frame;
|
|
127
|
+
entry.handle = handle;
|
|
128
|
+
stack.push(entry);
|
|
129
|
+
syncBackdrop();
|
|
130
|
+
ensureEscapeListener();
|
|
131
|
+
return handle;
|
|
132
|
+
}
|
|
133
|
+
function closeAll() {
|
|
134
|
+
while (stack.length > 0) {
|
|
135
|
+
removeEntry(stack[stack.length - 1]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export const modalManager = {
|
|
139
|
+
open: openModal,
|
|
140
|
+
closeAll,
|
|
141
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
import type { PopupHandle, PopupOptions } from './types';
|
|
3
|
+
export interface PopupManager {
|
|
4
|
+
show<P extends Record<string, unknown>>(Content: Component<P & {
|
|
5
|
+
close: () => void;
|
|
6
|
+
}>, options: PopupOptions, props?: P): PopupHandle;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const popupManager: PopupManager;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Popup manager — anchored, non-stacking, outside-click-dismissable.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* popupManager.show(Content, { anchor, placement? }, props?) → PopupHandle
|
|
6
|
+
* popupManager.close()
|
|
7
|
+
*
|
|
8
|
+
* Semantics (from docs/design/layout.md):
|
|
9
|
+
* - Popups do NOT stack. Opening a second popup dismisses the first.
|
|
10
|
+
* - Clicking outside the popup dismisses it.
|
|
11
|
+
* - Pressing Escape dismisses it.
|
|
12
|
+
* - The caller provides an HTMLElement anchor; the popup positions
|
|
13
|
+
* itself relative to the anchor's current viewport rect.
|
|
14
|
+
*
|
|
15
|
+
* Implementation notes:
|
|
16
|
+
* - The manager keeps at most one active entry. show() closes any
|
|
17
|
+
* prior entry before opening a new one — so callers can wire a
|
|
18
|
+
* menu button to `shell.popup.show(...)` without worrying about
|
|
19
|
+
* toggling themselves.
|
|
20
|
+
* - Dismissal listeners (pointerdown for outside-click, keydown for
|
|
21
|
+
* Escape) are installed when the popup opens and removed on close.
|
|
22
|
+
* Outside-click detection uses the mounted frame element's DOM
|
|
23
|
+
* containment check, so nested popups-inside-content still click-
|
|
24
|
+
* through without closing (there are no nested popups in phase 5,
|
|
25
|
+
* but the check is cheap and correct).
|
|
26
|
+
* - close() is idempotent.
|
|
27
|
+
*/
|
|
28
|
+
import { mount, unmount } from 'svelte';
|
|
29
|
+
import PopupFrame from './PopupFrame.svelte';
|
|
30
|
+
import { getLayerRoot } from './roots';
|
|
31
|
+
let current = null;
|
|
32
|
+
function onDocumentPointerDown(e) {
|
|
33
|
+
if (!current)
|
|
34
|
+
return;
|
|
35
|
+
const target = e.target;
|
|
36
|
+
if (target && current.host.contains(target))
|
|
37
|
+
return;
|
|
38
|
+
current.handle.close();
|
|
39
|
+
}
|
|
40
|
+
function onDocumentKeydown(e) {
|
|
41
|
+
if (!current)
|
|
42
|
+
return;
|
|
43
|
+
if (e.key !== 'Escape')
|
|
44
|
+
return;
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
current.handle.close();
|
|
48
|
+
}
|
|
49
|
+
function installDismissListeners() {
|
|
50
|
+
// `true` capture phase so we see the event before view-level handlers
|
|
51
|
+
// that might stopPropagation.
|
|
52
|
+
document.addEventListener('pointerdown', onDocumentPointerDown, true);
|
|
53
|
+
document.addEventListener('keydown', onDocumentKeydown, true);
|
|
54
|
+
}
|
|
55
|
+
function removeDismissListeners() {
|
|
56
|
+
document.removeEventListener('pointerdown', onDocumentPointerDown, true);
|
|
57
|
+
document.removeEventListener('keydown', onDocumentKeydown, true);
|
|
58
|
+
}
|
|
59
|
+
function removeEntry(entry) {
|
|
60
|
+
if (current !== entry)
|
|
61
|
+
return; // already closed / superseded
|
|
62
|
+
current = null;
|
|
63
|
+
removeDismissListeners();
|
|
64
|
+
unmount(entry.frame);
|
|
65
|
+
entry.host.remove();
|
|
66
|
+
}
|
|
67
|
+
function showPopup(Content, options, props) {
|
|
68
|
+
// Non-stacking: dismiss any existing popup first.
|
|
69
|
+
if (current)
|
|
70
|
+
removeEntry(current);
|
|
71
|
+
const root = getLayerRoot('popup');
|
|
72
|
+
const host = document.createElement('div');
|
|
73
|
+
host.className = 'sh3-popup-host';
|
|
74
|
+
host.style.position = 'absolute';
|
|
75
|
+
host.style.inset = '0';
|
|
76
|
+
host.style.pointerEvents = 'none'; // only the frame captures pointer events
|
|
77
|
+
root.appendChild(host);
|
|
78
|
+
const anchorRect = options.anchor.getBoundingClientRect();
|
|
79
|
+
const entry = {};
|
|
80
|
+
const handle = {
|
|
81
|
+
close: () => removeEntry(entry),
|
|
82
|
+
};
|
|
83
|
+
const frame = mount(PopupFrame, {
|
|
84
|
+
target: host,
|
|
85
|
+
props: {
|
|
86
|
+
Content: Content,
|
|
87
|
+
contentProps: (props !== null && props !== void 0 ? props : {}),
|
|
88
|
+
anchorRect,
|
|
89
|
+
close: handle.close,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
entry.host = host;
|
|
93
|
+
entry.frame = frame;
|
|
94
|
+
entry.handle = handle;
|
|
95
|
+
current = entry;
|
|
96
|
+
// Defer listener install by a microtask so the same pointerdown that
|
|
97
|
+
// opened the popup (from a button click) doesn't instantly dismiss it.
|
|
98
|
+
queueMicrotask(installDismissListeners);
|
|
99
|
+
return handle;
|
|
100
|
+
}
|
|
101
|
+
function closeCurrent() {
|
|
102
|
+
if (current)
|
|
103
|
+
removeEntry(current);
|
|
104
|
+
}
|
|
105
|
+
export const popupManager = {
|
|
106
|
+
show: showPopup,
|
|
107
|
+
close: closeCurrent,
|
|
108
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OverlayLayer } from './types';
|
|
2
|
+
export declare function registerLayerRoot(layer: OverlayLayer, el: HTMLElement): void;
|
|
3
|
+
export declare function unregisterLayerRoot(layer: OverlayLayer): void;
|
|
4
|
+
export declare function getLayerRoot(layer: OverlayLayer): HTMLElement;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Layer root registry.
|
|
3
|
+
*
|
|
4
|
+
* Shell.svelte owns the six overlay root <div>s (one per layer). Overlay
|
|
5
|
+
* managers — which are plain TypeScript modules, not Svelte components —
|
|
6
|
+
* need DOM references to those roots so they can append transient content
|
|
7
|
+
* (modals, popups, toasts) into the correct stacking context.
|
|
8
|
+
*
|
|
9
|
+
* Shell.svelte calls `registerLayerRoot(name, el)` on each root during its
|
|
10
|
+
* own mount effect. Managers call `getLayerRoot(name)` lazily the first
|
|
11
|
+
* time they need to open something, which is always *after* Shell has
|
|
12
|
+
* rendered because overlay triggers originate from views mounted into the
|
|
13
|
+
* layout tree. No race; no DOM queries.
|
|
14
|
+
*
|
|
15
|
+
* Unregister is exposed too so tests and hot-reload can reset the registry.
|
|
16
|
+
*/
|
|
17
|
+
const roots = {};
|
|
18
|
+
export function registerLayerRoot(layer, el) {
|
|
19
|
+
roots[layer] = el;
|
|
20
|
+
}
|
|
21
|
+
export function unregisterLayerRoot(layer) {
|
|
22
|
+
delete roots[layer];
|
|
23
|
+
}
|
|
24
|
+
export function getLayerRoot(layer) {
|
|
25
|
+
const el = roots[layer];
|
|
26
|
+
if (!el) {
|
|
27
|
+
throw new Error(`Overlay layer "${layer}" root is not registered — ` +
|
|
28
|
+
`Shell.svelte must mount before opening overlays on this layer.`);
|
|
29
|
+
}
|
|
30
|
+
return el;
|
|
31
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Toast manager — auto-dismissing non-interactive notifications.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* toastManager.notify(message, options?) → ToastHandle
|
|
6
|
+
* toastManager.clear()
|
|
7
|
+
*
|
|
8
|
+
* Semantics (from docs/design/layout.md):
|
|
9
|
+
* - Toasts queue and stack vertically at the bottom-right of the shell.
|
|
10
|
+
* - Each toast auto-dismisses after `duration` ms (default 3000).
|
|
11
|
+
* `duration: Infinity` pins the toast until the caller calls close().
|
|
12
|
+
* - Clicking a toast dismisses it immediately.
|
|
13
|
+
* - Level styling: info / warn / error / success.
|
|
14
|
+
*
|
|
15
|
+
* Implementation notes:
|
|
16
|
+
* - A lazy layout container is appended to the layer-5 root on first
|
|
17
|
+
* notify; it's a flex column anchored to the bottom-right with a
|
|
18
|
+
* small gap between toasts.
|
|
19
|
+
* - Each toast is a standalone mount — simpler than a single list
|
|
20
|
+
* component tracking an array, and naturally handles independent
|
|
21
|
+
* fade-in animations per item.
|
|
22
|
+
* - The manager tracks entries for `clear()` and for idempotent close.
|
|
23
|
+
*/
|
|
24
|
+
import { mount, unmount } from 'svelte';
|
|
25
|
+
import ToastItem from './ToastItem.svelte';
|
|
26
|
+
import { getLayerRoot } from './roots';
|
|
27
|
+
const entries = new Set();
|
|
28
|
+
let container = null;
|
|
29
|
+
function ensureContainer() {
|
|
30
|
+
if (container && container.isConnected)
|
|
31
|
+
return container;
|
|
32
|
+
const root = getLayerRoot('toast');
|
|
33
|
+
container = document.createElement('div');
|
|
34
|
+
container.className = 'sh3-toast-stack';
|
|
35
|
+
container.style.position = 'absolute';
|
|
36
|
+
container.style.right = '16px';
|
|
37
|
+
container.style.bottom = '16px';
|
|
38
|
+
container.style.display = 'flex';
|
|
39
|
+
container.style.flexDirection = 'column-reverse';
|
|
40
|
+
container.style.gap = '6px';
|
|
41
|
+
container.style.pointerEvents = 'none';
|
|
42
|
+
root.appendChild(container);
|
|
43
|
+
return container;
|
|
44
|
+
}
|
|
45
|
+
function removeEntry(entry) {
|
|
46
|
+
if (!entries.has(entry))
|
|
47
|
+
return;
|
|
48
|
+
entries.delete(entry);
|
|
49
|
+
if (entry.timer !== null) {
|
|
50
|
+
clearTimeout(entry.timer);
|
|
51
|
+
entry.timer = null;
|
|
52
|
+
}
|
|
53
|
+
unmount(entry.instance);
|
|
54
|
+
entry.host.remove();
|
|
55
|
+
}
|
|
56
|
+
function notify(message, options = {}) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
const level = (_a = options.level) !== null && _a !== void 0 ? _a : 'info';
|
|
59
|
+
const duration = (_b = options.duration) !== null && _b !== void 0 ? _b : 3000;
|
|
60
|
+
const parent = ensureContainer();
|
|
61
|
+
const host = document.createElement('div');
|
|
62
|
+
host.className = 'sh3-toast-host';
|
|
63
|
+
parent.appendChild(host);
|
|
64
|
+
const entry = {};
|
|
65
|
+
const handle = {
|
|
66
|
+
close: () => removeEntry(entry),
|
|
67
|
+
};
|
|
68
|
+
const instance = mount(ToastItem, {
|
|
69
|
+
target: host,
|
|
70
|
+
props: {
|
|
71
|
+
message,
|
|
72
|
+
level,
|
|
73
|
+
close: handle.close,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
entry.host = host;
|
|
77
|
+
entry.instance = instance;
|
|
78
|
+
entry.handle = handle;
|
|
79
|
+
entry.timer =
|
|
80
|
+
Number.isFinite(duration) && duration > 0
|
|
81
|
+
? setTimeout(() => removeEntry(entry), duration)
|
|
82
|
+
: null;
|
|
83
|
+
entries.add(entry);
|
|
84
|
+
return handle;
|
|
85
|
+
}
|
|
86
|
+
function clear() {
|
|
87
|
+
for (const entry of [...entries])
|
|
88
|
+
removeEntry(entry);
|
|
89
|
+
}
|
|
90
|
+
export const toastManager = {
|
|
91
|
+
notify,
|
|
92
|
+
clear,
|
|
93
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type OverlayLayer = 'floating' | 'drag-preview' | 'popup' | 'modal' | 'toast' | 'command';
|
|
2
|
+
/** A handle returned by every overlay opener. Calling close() is idempotent. */
|
|
3
|
+
export interface OverlayHandle {
|
|
4
|
+
close(): void;
|
|
5
|
+
}
|
|
6
|
+
export type ModalHandle = OverlayHandle;
|
|
7
|
+
export type PopupHandle = OverlayHandle;
|
|
8
|
+
export type ToastHandle = OverlayHandle;
|
|
9
|
+
export type ToastLevel = 'info' | 'warn' | 'error' | 'success';
|
|
10
|
+
/** Where a popup should sit relative to its anchor. Phase 5 ships bottom-start. */
|
|
11
|
+
export type PopupPlacement = 'bottom-start';
|
|
12
|
+
export interface PopupOptions {
|
|
13
|
+
anchor: HTMLElement;
|
|
14
|
+
placement?: PopupPlacement;
|
|
15
|
+
}
|
|
16
|
+
export interface ToastOptions {
|
|
17
|
+
level?: ToastLevel;
|
|
18
|
+
/** Auto-dismiss after this many ms. `Infinity` disables auto-dismiss. */
|
|
19
|
+
duration?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ModalOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Inline style string applied to the modal's dialog box. Lets callers
|
|
24
|
+
* override the default centered-auto-sized layout for custom sizing
|
|
25
|
+
* and positioning. Setting `position: absolute; top: …; left: …`
|
|
26
|
+
* escapes the frame's grid centering and places the box at the given
|
|
27
|
+
* viewport-relative coordinates. Useful for test scaffolding and for
|
|
28
|
+
* views like non-centered palettes or inspectors.
|
|
29
|
+
*/
|
|
30
|
+
boxStyle?: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Overlay API types — shared between layer managers.
|
|
3
|
+
*
|
|
4
|
+
* The "layer" concept comes from docs/design/layout.md: overlays live
|
|
5
|
+
* above the docked layout in an explicit numbered stack (0 = docked,
|
|
6
|
+
* 1 = floating, 2 = drag preview, 3 = popup, 4 = modal, 5 = toast,
|
|
7
|
+
* 6 = command palette). Phase 5 wires up layers 3/4/5; the rest are
|
|
8
|
+
* stubbed as DOM roots in Shell.svelte but have no managers yet.
|
|
9
|
+
*
|
|
10
|
+
* No component in the codebase writes a z-index directly — the only
|
|
11
|
+
* z-index values live on the overlay root divs in Shell.svelte driven
|
|
12
|
+
* by the --shell-z-layer-N tokens. Overlay content is appended into the
|
|
13
|
+
* corresponding layer root and inherits its stacking context.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Backend } from '../state/types';
|
|
2
|
+
export interface PlatformBackends {
|
|
3
|
+
workspace: Backend;
|
|
4
|
+
user: Backend;
|
|
5
|
+
}
|
|
6
|
+
export interface PlatformResult {
|
|
7
|
+
backends: PlatformBackends | null;
|
|
8
|
+
localOwner: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolvePlatform(): Promise<PlatformResult>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Platform detection and backend resolution.
|
|
3
|
+
*
|
|
4
|
+
* Called once at boot, before bootstrap(). Detects whether we're running
|
|
5
|
+
* inside a Tauri webview by attempting to dynamically import the Tauri
|
|
6
|
+
* store backend. If the import fails (web build, or Tauri APIs absent),
|
|
7
|
+
* falls back to default localStorage backends.
|
|
8
|
+
*
|
|
9
|
+
* Also resolves localOwner — true in Tauri (user owns the device) or
|
|
10
|
+
* when running in a dev build (as reported by esm-env's DEV). Production
|
|
11
|
+
* web builds are never local-owner.
|
|
12
|
+
*
|
|
13
|
+
* Vite code-splits the Tauri path into a separate chunk that is never
|
|
14
|
+
* loaded in web builds (the dynamic import fails at runtime).
|
|
15
|
+
*/
|
|
16
|
+
import { DEV } from 'esm-env';
|
|
17
|
+
export async function resolvePlatform() {
|
|
18
|
+
try {
|
|
19
|
+
const { TauriStoreBackend } = await import('./tauri-backend');
|
|
20
|
+
const workspace = new TauriStoreBackend('workspace');
|
|
21
|
+
const user = new TauriStoreBackend('user');
|
|
22
|
+
// Ensure stores are loaded from disk before returning.
|
|
23
|
+
await Promise.all([workspace.init(), user.init()]);
|
|
24
|
+
return { backends: { workspace, user }, localOwner: true };
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// Not in Tauri — fall back to default web backends.
|
|
28
|
+
// Local-owner if running in a dev build. `DEV` comes from esm-env,
|
|
29
|
+
// which works with all bundlers (and at runtime in plain node), so
|
|
30
|
+
// sh3-core stays compatible with non-Vite consumers.
|
|
31
|
+
return { backends: null, localOwner: DEV };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Backend } from '../state/types';
|
|
2
|
+
export declare class TauriStoreBackend implements Backend {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(zoneName: string);
|
|
5
|
+
read(shardId: string): unknown | undefined;
|
|
6
|
+
write(shardId: string, value: unknown): void;
|
|
7
|
+
delete(shardId: string): void;
|
|
8
|
+
list(): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Load the store from disk into the local cache. Must be called once
|
|
11
|
+
* before read/list return meaningful data. Called by the platform
|
|
12
|
+
* resolver at boot.
|
|
13
|
+
*/
|
|
14
|
+
init(): Promise<void>;
|
|
15
|
+
}
|