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.
Files changed (242) hide show
  1. package/README.md +9 -0
  2. package/dist/Shell.svelte +283 -0
  3. package/dist/Shell.svelte.d.ts +5 -0
  4. package/dist/api.d.ts +28 -0
  5. package/dist/api.js +50 -0
  6. package/dist/app/admin/ApiKeysView.svelte +169 -0
  7. package/dist/app/admin/ApiKeysView.svelte.d.ts +3 -0
  8. package/dist/app/admin/AuthSettingsView.svelte +105 -0
  9. package/dist/app/admin/AuthSettingsView.svelte.d.ts +3 -0
  10. package/dist/app/admin/SystemView.svelte +73 -0
  11. package/dist/app/admin/SystemView.svelte.d.ts +3 -0
  12. package/dist/app/admin/UsersView.svelte +188 -0
  13. package/dist/app/admin/UsersView.svelte.d.ts +3 -0
  14. package/dist/app/admin/adminApp.d.ts +7 -0
  15. package/dist/app/admin/adminApp.js +25 -0
  16. package/dist/app/admin/adminShard.svelte.d.ts +4 -0
  17. package/dist/app/admin/adminShard.svelte.js +62 -0
  18. package/dist/app/store/InstalledView.svelte +246 -0
  19. package/dist/app/store/InstalledView.svelte.d.ts +3 -0
  20. package/dist/app/store/StoreView.svelte +522 -0
  21. package/dist/app/store/StoreView.svelte.d.ts +3 -0
  22. package/dist/app/store/storeApp.d.ts +10 -0
  23. package/dist/app/store/storeApp.js +26 -0
  24. package/dist/app/store/storeShard.svelte.d.ts +38 -0
  25. package/dist/app/store/storeShard.svelte.js +218 -0
  26. package/dist/apps/lifecycle.d.ts +42 -0
  27. package/dist/apps/lifecycle.js +184 -0
  28. package/dist/apps/registry.svelte.d.ts +40 -0
  29. package/dist/apps/registry.svelte.js +59 -0
  30. package/dist/apps/terminal/manifest.d.ts +8 -0
  31. package/dist/apps/terminal/manifest.js +13 -0
  32. package/dist/apps/terminal/terminal-app.d.ts +7 -0
  33. package/dist/apps/terminal/terminal-app.js +14 -0
  34. package/dist/apps/types.d.ts +93 -0
  35. package/dist/apps/types.js +10 -0
  36. package/dist/artifact.d.ts +32 -0
  37. package/dist/artifact.js +1 -0
  38. package/dist/assets/SH3.png +0 -0
  39. package/dist/assets/icons.svg +1126 -0
  40. package/dist/assets.d.ts +13 -0
  41. package/dist/auth/GuestBanner.svelte +134 -0
  42. package/dist/auth/GuestBanner.svelte.d.ts +3 -0
  43. package/dist/auth/SignInWall.svelte +203 -0
  44. package/dist/auth/SignInWall.svelte.d.ts +7 -0
  45. package/dist/auth/auth.svelte.d.ts +69 -0
  46. package/dist/auth/auth.svelte.js +165 -0
  47. package/dist/auth/index.d.ts +2 -0
  48. package/dist/auth/index.js +1 -0
  49. package/dist/auth/types.d.ts +41 -0
  50. package/dist/auth/types.js +6 -0
  51. package/dist/build.d.ts +49 -0
  52. package/dist/build.js +236 -0
  53. package/dist/contract.d.ts +20 -0
  54. package/dist/contract.js +28 -0
  55. package/dist/createShell.d.ts +24 -0
  56. package/dist/createShell.js +131 -0
  57. package/dist/documents/backends.d.ts +17 -0
  58. package/dist/documents/backends.js +156 -0
  59. package/dist/documents/config.d.ts +7 -0
  60. package/dist/documents/config.js +27 -0
  61. package/dist/documents/handle.d.ts +6 -0
  62. package/dist/documents/handle.js +154 -0
  63. package/dist/documents/http-backend.d.ts +22 -0
  64. package/dist/documents/http-backend.js +78 -0
  65. package/dist/documents/index.d.ts +6 -0
  66. package/dist/documents/index.js +8 -0
  67. package/dist/documents/notifications.d.ts +9 -0
  68. package/dist/documents/notifications.js +39 -0
  69. package/dist/documents/types.d.ts +97 -0
  70. package/dist/documents/types.js +12 -0
  71. package/dist/env/client.d.ts +44 -0
  72. package/dist/env/client.js +106 -0
  73. package/dist/env/index.d.ts +2 -0
  74. package/dist/env/index.js +1 -0
  75. package/dist/env/types.d.ts +12 -0
  76. package/dist/env/types.js +8 -0
  77. package/dist/host-entry.d.ts +13 -0
  78. package/dist/host-entry.js +17 -0
  79. package/dist/host.d.ts +15 -0
  80. package/dist/host.js +86 -0
  81. package/dist/index.d.ts +4 -0
  82. package/dist/index.js +14 -0
  83. package/dist/layout/DragPreview.svelte +63 -0
  84. package/dist/layout/DragPreview.svelte.d.ts +3 -0
  85. package/dist/layout/LayoutRenderer.svelte +262 -0
  86. package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
  87. package/dist/layout/SlotContainer.svelte +140 -0
  88. package/dist/layout/SlotContainer.svelte.d.ts +8 -0
  89. package/dist/layout/SlotDropZone.svelte +122 -0
  90. package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
  91. package/dist/layout/drag.svelte.d.ts +45 -0
  92. package/dist/layout/drag.svelte.js +200 -0
  93. package/dist/layout/inspection.d.ts +72 -0
  94. package/dist/layout/inspection.js +209 -0
  95. package/dist/layout/ops.d.ts +100 -0
  96. package/dist/layout/ops.js +310 -0
  97. package/dist/layout/slotHostPool.svelte.d.ts +36 -0
  98. package/dist/layout/slotHostPool.svelte.js +229 -0
  99. package/dist/layout/store.svelte.d.ts +39 -0
  100. package/dist/layout/store.svelte.js +153 -0
  101. package/dist/layout/tree-walk.d.ts +15 -0
  102. package/dist/layout/tree-walk.js +33 -0
  103. package/dist/layout/types.d.ts +108 -0
  104. package/dist/layout/types.js +25 -0
  105. package/dist/migrations/shell-rename.d.ts +16 -0
  106. package/dist/migrations/shell-rename.js +48 -0
  107. package/dist/overlays/ModalFrame.svelte +87 -0
  108. package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
  109. package/dist/overlays/PopupFrame.svelte +85 -0
  110. package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
  111. package/dist/overlays/ToastItem.svelte +77 -0
  112. package/dist/overlays/ToastItem.svelte.d.ts +9 -0
  113. package/dist/overlays/focusTrap.d.ts +1 -0
  114. package/dist/overlays/focusTrap.js +64 -0
  115. package/dist/overlays/modal.d.ts +9 -0
  116. package/dist/overlays/modal.js +141 -0
  117. package/dist/overlays/popup.d.ts +9 -0
  118. package/dist/overlays/popup.js +108 -0
  119. package/dist/overlays/roots.d.ts +4 -0
  120. package/dist/overlays/roots.js +31 -0
  121. package/dist/overlays/toast.d.ts +6 -0
  122. package/dist/overlays/toast.js +93 -0
  123. package/dist/overlays/types.d.ts +31 -0
  124. package/dist/overlays/types.js +15 -0
  125. package/dist/platform/index.d.ts +10 -0
  126. package/dist/platform/index.js +33 -0
  127. package/dist/platform/tauri-backend.d.ts +15 -0
  128. package/dist/platform/tauri-backend.js +58 -0
  129. package/dist/primitives/.gitkeep +0 -0
  130. package/dist/primitives/ResizableSplitter.svelte +333 -0
  131. package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
  132. package/dist/primitives/TabbedPanel.svelte +305 -0
  133. package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
  134. package/dist/primitives/base.css +42 -0
  135. package/dist/registry/client.d.ts +74 -0
  136. package/dist/registry/client.js +117 -0
  137. package/dist/registry/index.d.ts +13 -0
  138. package/dist/registry/index.js +14 -0
  139. package/dist/registry/installer.d.ts +53 -0
  140. package/dist/registry/installer.js +168 -0
  141. package/dist/registry/integrity.d.ts +32 -0
  142. package/dist/registry/integrity.js +92 -0
  143. package/dist/registry/loader.d.ts +50 -0
  144. package/dist/registry/loader.js +145 -0
  145. package/dist/registry/schema.d.ts +47 -0
  146. package/dist/registry/schema.js +185 -0
  147. package/dist/registry/storage.d.ts +37 -0
  148. package/dist/registry/storage.js +101 -0
  149. package/dist/registry/types.d.ts +262 -0
  150. package/dist/registry/types.js +14 -0
  151. package/dist/server-shard/types.d.ts +67 -0
  152. package/dist/server-shard/types.js +13 -0
  153. package/dist/sh3core-shard/ShellHome.svelte +192 -0
  154. package/dist/sh3core-shard/ShellHome.svelte.d.ts +3 -0
  155. package/dist/sh3core-shard/ShellTitle.svelte +171 -0
  156. package/dist/sh3core-shard/ShellTitle.svelte.d.ts +3 -0
  157. package/dist/sh3core-shard/sh3coreShard.svelte.d.ts +2 -0
  158. package/dist/sh3core-shard/sh3coreShard.svelte.js +53 -0
  159. package/dist/shards/activate.svelte.d.ts +52 -0
  160. package/dist/shards/activate.svelte.js +186 -0
  161. package/dist/shards/registry.d.ts +4 -0
  162. package/dist/shards/registry.js +28 -0
  163. package/dist/shards/types.d.ts +207 -0
  164. package/dist/shards/types.js +20 -0
  165. package/dist/shell-shard/InputLine.svelte +133 -0
  166. package/dist/shell-shard/InputLine.svelte.d.ts +11 -0
  167. package/dist/shell-shard/ScrollbackView.svelte +47 -0
  168. package/dist/shell-shard/ScrollbackView.svelte.d.ts +7 -0
  169. package/dist/shell-shard/Terminal.svelte +122 -0
  170. package/dist/shell-shard/Terminal.svelte.d.ts +8 -0
  171. package/dist/shell-shard/entries/PromptEntry.svelte +25 -0
  172. package/dist/shell-shard/entries/PromptEntry.svelte.d.ts +7 -0
  173. package/dist/shell-shard/entries/RichEntry.svelte +19 -0
  174. package/dist/shell-shard/entries/RichEntry.svelte.d.ts +8 -0
  175. package/dist/shell-shard/entries/StatusEntry.svelte +22 -0
  176. package/dist/shell-shard/entries/StatusEntry.svelte.d.ts +7 -0
  177. package/dist/shell-shard/entries/TextEntry.svelte +25 -0
  178. package/dist/shell-shard/entries/TextEntry.svelte.d.ts +7 -0
  179. package/dist/shell-shard/manifest.d.ts +2 -0
  180. package/dist/shell-shard/manifest.js +11 -0
  181. package/dist/shell-shard/protocol.d.ts +90 -0
  182. package/dist/shell-shard/protocol.js +11 -0
  183. package/dist/shell-shard/registry.d.ts +69 -0
  184. package/dist/shell-shard/registry.js +47 -0
  185. package/dist/shell-shard/rich/AppCard.svelte +25 -0
  186. package/dist/shell-shard/rich/AppCard.svelte.d.ts +10 -0
  187. package/dist/shell-shard/rich/AppsTable.svelte +29 -0
  188. package/dist/shell-shard/rich/AppsTable.svelte.d.ts +12 -0
  189. package/dist/shell-shard/rich/EnvTable.svelte +27 -0
  190. package/dist/shell-shard/rich/EnvTable.svelte.d.ts +8 -0
  191. package/dist/shell-shard/rich/HelpTable.svelte +29 -0
  192. package/dist/shell-shard/rich/HelpTable.svelte.d.ts +12 -0
  193. package/dist/shell-shard/rich/HistoryList.svelte +37 -0
  194. package/dist/shell-shard/rich/HistoryList.svelte.d.ts +9 -0
  195. package/dist/shell-shard/rich/ShardsTable.svelte +28 -0
  196. package/dist/shell-shard/rich/ShardsTable.svelte.d.ts +12 -0
  197. package/dist/shell-shard/rich/ViewsTable.svelte +31 -0
  198. package/dist/shell-shard/rich/ViewsTable.svelte.d.ts +13 -0
  199. package/dist/shell-shard/rich/ZoneTree.svelte +19 -0
  200. package/dist/shell-shard/rich/ZoneTree.svelte.d.ts +8 -0
  201. package/dist/shell-shard/rich/ZonesTable.svelte +27 -0
  202. package/dist/shell-shard/rich/ZonesTable.svelte.d.ts +11 -0
  203. package/dist/shell-shard/scrollback.svelte.d.ts +36 -0
  204. package/dist/shell-shard/scrollback.svelte.js +43 -0
  205. package/dist/shell-shard/session-client.svelte.d.ts +23 -0
  206. package/dist/shell-shard/session-client.svelte.js +120 -0
  207. package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
  208. package/dist/shell-shard/shellShard.svelte.js +139 -0
  209. package/dist/shell-shard/verbs/apps.d.ts +3 -0
  210. package/dist/shell-shard/verbs/apps.js +50 -0
  211. package/dist/shell-shard/verbs/clear.d.ts +2 -0
  212. package/dist/shell-shard/verbs/clear.js +7 -0
  213. package/dist/shell-shard/verbs/help.d.ts +2 -0
  214. package/dist/shell-shard/verbs/help.js +21 -0
  215. package/dist/shell-shard/verbs/history.d.ts +2 -0
  216. package/dist/shell-shard/verbs/history.js +20 -0
  217. package/dist/shell-shard/verbs/index.d.ts +2 -0
  218. package/dist/shell-shard/verbs/index.js +29 -0
  219. package/dist/shell-shard/verbs/session.d.ts +5 -0
  220. package/dist/shell-shard/verbs/session.js +65 -0
  221. package/dist/shell-shard/verbs/shards.d.ts +2 -0
  222. package/dist/shell-shard/verbs/shards.js +14 -0
  223. package/dist/shell-shard/verbs/views.d.ts +4 -0
  224. package/dist/shell-shard/verbs/views.js +90 -0
  225. package/dist/shell-shard/verbs/zones.d.ts +3 -0
  226. package/dist/shell-shard/verbs/zones.js +38 -0
  227. package/dist/shellRuntime.svelte.d.ts +27 -0
  228. package/dist/shellRuntime.svelte.js +27 -0
  229. package/dist/state/backends.d.ts +26 -0
  230. package/dist/state/backends.js +99 -0
  231. package/dist/state/manage.d.ts +14 -0
  232. package/dist/state/manage.js +40 -0
  233. package/dist/state/types.d.ts +55 -0
  234. package/dist/state/types.js +17 -0
  235. package/dist/state/zones.svelte.d.ts +53 -0
  236. package/dist/state/zones.svelte.js +141 -0
  237. package/dist/theme.d.ts +28 -0
  238. package/dist/theme.js +92 -0
  239. package/dist/tokens.css +102 -0
  240. package/dist/version.d.ts +2 -0
  241. package/dist/version.js +2 -0
  242. 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,6 @@
1
+ import type { ToastHandle, ToastOptions } from './types';
2
+ export interface ToastManager {
3
+ notify(message: string, options?: ToastOptions): ToastHandle;
4
+ clear(): void;
5
+ }
6
+ export declare const toastManager: ToastManager;
@@ -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
+ }