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,13 @@
1
+ export { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner } from './host';
2
+ export type { BootstrapConfig } from './host';
3
+ export { __setTenantId, __setDocumentBackend } from './host';
4
+ export type { Backend } from './state/types';
5
+ export type { DocumentBackend } from './documents/types';
6
+ export { HttpDocumentBackend } from './documents/http-backend';
7
+ export { __setEnvServerUrl } from './env/index';
8
+ export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledPackages, } from './registry/index';
9
+ export type { InstalledPackage, InstallResult, PackageMeta } from './registry/types';
10
+ export { initFromBoot, login, logout, register, setLocalOwner as setLocalOwnerAuth } from './auth/index';
11
+ export type { AuthUser, AuthSession, BootConfig, GlobalSettings } from './auth/types';
12
+ export { createShell } from './createShell';
13
+ export type { ShellConfig } from './createShell';
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Host-process entry for the `sh3-core` package.
3
+ *
4
+ * This file is what `import ... from 'sh3-core/host'` resolves to. Only code that
5
+ * boots an SH3 shell (a main.ts that mounts Shell and registers shards/apps)
6
+ * should touch this path. Shards and apps must not import from here.
7
+ */
8
+ export { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner } from './host';
9
+ export { __setTenantId, __setDocumentBackend } from './host';
10
+ export { HttpDocumentBackend } from './documents/http-backend';
11
+ export { __setEnvServerUrl } from './env/index';
12
+ // Install API (host-only).
13
+ export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledPackages, } from './registry/index';
14
+ // Auth (host-only — session lifecycle, boot initialization).
15
+ export { initFromBoot, login, logout, register, setLocalOwner as setLocalOwnerAuth } from './auth/index';
16
+ // Shell boot factory.
17
+ export { createShell } from './createShell';
package/dist/host.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { registerShard as registerShardInternal } from './shards/activate.svelte';
2
+ import { registerApp } from './apps/registry.svelte';
3
+ import { __setBackend } from './state/zones.svelte';
4
+ import { setLocalOwner } from './auth/index';
5
+ export { __setBackend };
6
+ export { setLocalOwner };
7
+ export { __setTenantId, __setDocumentBackend } from './documents/config';
8
+ export declare function registerShard(shard: Parameters<typeof registerShardInternal>[0]): void;
9
+ export { registerApp };
10
+ export interface BootstrapConfig {
11
+ /** Framework shard IDs to skip registration for */
12
+ excludeShards?: string[];
13
+ }
14
+ export declare function bootstrap(config?: BootstrapConfig): Promise<void>;
15
+ export { installPackage, listInstalledPackages } from './registry/installer';
package/dist/host.js ADDED
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Host surface — functions `main.ts` (and only main.ts) calls.
3
+ *
4
+ * Registration APIs (`registerShard`, `registerApp`) are data-only and
5
+ * safe to call at any time; a future runtime loader uses them identically
6
+ * to hot-install registration at runtime.
7
+ *
8
+ * `bootstrap()` runs the post-registration boot sequence: it registers
9
+ * framework-owned shards and apps (filtering any the host excludes via
10
+ * BootstrapConfig), walks the registered-shards map and activates every
11
+ * self-starting shard, then reads the last-app user-zone entry and
12
+ * either launches that app or leaves the shell on home.
13
+ *
14
+ * This file is intentionally NOT re-exported through `api.ts`. The
15
+ * import-hygiene rule is: shards and apps import from `api.ts`, the host
16
+ * imports from `host.ts`.
17
+ */
18
+ import { registerShard as registerShardInternal, activateShard, registeredShards, } from './shards/activate.svelte';
19
+ import { registerApp, registeredApps } from './apps/registry.svelte';
20
+ import { launchApp, readLastApp } from './apps/lifecycle';
21
+ import { sh3coreShard } from './sh3core-shard/sh3coreShard.svelte';
22
+ import { shellShard } from './shell-shard/shellShard.svelte';
23
+ import { storeShard } from './app/store/storeShard.svelte';
24
+ import { __setBackend, backends } from './state/zones.svelte';
25
+ import { loadInstalledPackages } from './registry/installer';
26
+ import { setLocalOwner } from './auth/index';
27
+ import { storeApp } from './app/store/storeApp';
28
+ import { adminShard } from './app/admin/adminShard.svelte';
29
+ import { adminApp } from './app/admin/adminApp';
30
+ import { terminalApp } from './apps/terminal/terminal-app';
31
+ import { runShellRenameMigration, } from './migrations/shell-rename';
32
+ export { __setBackend };
33
+ export { setLocalOwner };
34
+ export { __setTenantId, __setDocumentBackend } from './documents/config';
35
+ export function registerShard(shard) {
36
+ registerShardInternal(shard);
37
+ }
38
+ export { registerApp };
39
+ /**
40
+ * Adapter from the workspace-zone backend to the minimal KV shape the
41
+ * `__shell__` → `__sh3core__` migration needs. Kept local to host.ts so
42
+ * the migration module stays backend-agnostic.
43
+ */
44
+ function createWorkspaceZoneAdapter() {
45
+ const backend = backends.workspace;
46
+ return {
47
+ keys: () => backend.list(),
48
+ read: (key) => backend.read(key),
49
+ write: (key, value) => backend.write(key, value),
50
+ delete: (key) => backend.delete(key),
51
+ };
52
+ }
53
+ export async function bootstrap(config) {
54
+ // Run before anything touches the workspace zone so renamed keys are
55
+ // already in place when shards activate.
56
+ if (typeof globalThis.localStorage !== 'undefined') {
57
+ runShellRenameMigration(createWorkspaceZoneAdapter(), globalThis.localStorage);
58
+ }
59
+ const exShards = new Set(config === null || config === void 0 ? void 0 : config.excludeShards);
60
+ // 1. Framework-owned shards
61
+ const frameworkShards = [sh3coreShard, shellShard, storeShard, adminShard];
62
+ for (const shard of frameworkShards) {
63
+ if (!exShards.has(shard.manifest.id)) {
64
+ registerShardInternal(shard);
65
+ }
66
+ }
67
+ // 2. Framework-shipped apps
68
+ const frameworkApps = [storeApp, adminApp, terminalApp];
69
+ for (const app of frameworkApps) {
70
+ registerApp(app);
71
+ }
72
+ // 3. Load any packages installed in a previous session from IndexedDB
73
+ await loadInstalledPackages();
74
+ // 4. Activate every self-starting shard
75
+ for (const [id, shard] of registeredShards) {
76
+ if (shard.autostart) {
77
+ await activateShard(id);
78
+ }
79
+ }
80
+ // 5. Read the last-active app from the user zone
81
+ const lastId = readLastApp();
82
+ if (lastId && registeredApps.has(lastId)) {
83
+ await launchApp(lastId);
84
+ }
85
+ }
86
+ export { installPackage, listInstalledPackages } from './registry/installer';
@@ -0,0 +1,4 @@
1
+ export * from './api';
2
+ export { default as Shell } from './Shell.svelte';
3
+ export type { ArtifactManifest } from './artifact';
4
+ export * from './shell-shard/protocol';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Main public entry for the `sh3-core` package.
3
+ *
4
+ * This file is what `import ... from 'sh3-core'` resolves to. It re-exports the
5
+ * phase 8 public surface (api.ts) plus the Shell host component. Shard and
6
+ * app authors import from here.
7
+ *
8
+ * Host-process functions (registerShard, registerApp, bootstrap) are
9
+ * deliberately NOT exported here — they live at `sh3-core/host` because shards
10
+ * and apps must not register each other. See host-entry.ts.
11
+ */
12
+ export * from './api';
13
+ export { default as Shell } from './Shell.svelte';
14
+ export * from './shell-shard/protocol';
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ /*
3
+ * DragPreview — the ghost tab rendered on overlay layer 2 during a
4
+ * drag-reorganize gesture.
5
+ *
6
+ * The composition root mounts this component once into the drag-
7
+ * preview layer root. It reads `dragState` and renders itself only
8
+ * while `phase === 'dragging'`. Position tracks `dragState.pointerX`
9
+ * / `pointerY` minus the drag source's initial grab offset, so the
10
+ * ghost appears "held" at the same point on the tab where the user
11
+ * started dragging.
12
+ *
13
+ * Pointer events are disabled on the ghost itself — it must not
14
+ * intercept elementsFromPoint checks used by drop zones.
15
+ */
16
+
17
+ import { dragState } from './drag.svelte';
18
+
19
+ const visible = $derived(dragState.phase === 'dragging' && !!dragState.source);
20
+ const left = $derived(
21
+ dragState.source ? dragState.pointerX - dragState.source.offsetX : 0,
22
+ );
23
+ const top = $derived(
24
+ dragState.source ? dragState.pointerY - dragState.source.offsetY : 0,
25
+ );
26
+ const width = $derived(dragState.source?.startRect.width ?? 0);
27
+ const height = $derived(dragState.source?.startRect.height ?? 0);
28
+ const label = $derived(dragState.source?.entry.label ?? '');
29
+ const icon = $derived(dragState.source?.entry.icon);
30
+ </script>
31
+
32
+ {#if visible}
33
+ <div
34
+ class="drag-preview"
35
+ style="left: {left}px; top: {top}px; width: {width}px; height: {height}px;"
36
+ >
37
+ {#if icon}<span class="drag-preview-icon">{icon}</span>{/if}
38
+ <span class="drag-preview-label">{label}</span>
39
+ </div>
40
+ {/if}
41
+
42
+ <style>
43
+ .drag-preview {
44
+ position: absolute;
45
+ display: inline-flex;
46
+ align-items: center;
47
+ gap: var(--shell-pad-sm);
48
+ padding: var(--shell-pad-sm) var(--shell-pad-md);
49
+ background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated));
50
+ color: var(--shell-fg);
51
+ border: 1px solid var(--shell-accent);
52
+ border-radius: var(--shell-radius-sm);
53
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
54
+ font-size: 12px;
55
+ font-family: var(--shell-font-ui);
56
+ pointer-events: none;
57
+ opacity: 0.9;
58
+ /* The layer root inherits its z-index from --shell-z-layer-2 via
59
+ Shell.svelte; we don't set z-index here. */
60
+ }
61
+ .drag-preview-icon { font-size: 11px; }
62
+ .drag-preview-label { white-space: nowrap; }
63
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const DragPreview: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type DragPreview = ReturnType<typeof DragPreview>;
3
+ export default DragPreview;
@@ -0,0 +1,262 @@
1
+ <script lang="ts">
2
+ /*
3
+ * LayoutRenderer — recursive walker for a LayoutNode tree.
4
+ *
5
+ * Dispatches on the node kind:
6
+ * split → <ResizableSplitter> with one recursive <Self> per pane
7
+ * tabs → <TabbedPanel> with one <SlotContainer> per tab, a
8
+ * SlotDropZone overlay, and a drag controller wired to
9
+ * the drag engine
10
+ * slot → <SlotContainer> wrapped in a SlotDropZone
11
+ *
12
+ * Props: only `path` — a list of child indices from the root. The
13
+ * node itself is resolved as a $derived from `layoutStore.root` by
14
+ * walking the path. This is how we sidestep Svelte 5's
15
+ * `ownership_invalid_mutation` warning for a recursive component:
16
+ * - Passing `node` as a prop would make every mutation (e.g.
17
+ * `node.activeTab = i`) a write to a child-received prop, which
18
+ * the ownership tracker flags regardless of whether the
19
+ * underlying $state lives in a component or in a module.
20
+ * - Passing only `path` and deriving `node` locally means
21
+ * mutations go through a $derived of module state — no prop is
22
+ * involved, no ownership warning fires.
23
+ *
24
+ * `path` is a plain array, not reactive state, so there is no
25
+ * ownership concern on it. Each recursive call builds a new child
26
+ * path via `[...path, i]`.
27
+ */
28
+
29
+ import type { TabsNode, LayoutNode } from './types';
30
+ import ResizableSplitter from '../primitives/ResizableSplitter.svelte';
31
+ import TabbedPanel, { type TabDragController } from '../primitives/TabbedPanel.svelte';
32
+ import SlotContainer from './SlotContainer.svelte';
33
+ import SlotDropZone from './SlotDropZone.svelte';
34
+ import Self from './LayoutRenderer.svelte';
35
+ import { layoutStore } from './store.svelte';
36
+ import { nodeAtPath } from './ops';
37
+ import { isSlotClosable, isSlotDirty } from './slotHostPool.svelte';
38
+ import { closeTab } from './inspection';
39
+ import {
40
+ dragState,
41
+ beginTabDrag,
42
+ setDropTarget,
43
+ clearDropTarget,
44
+ suppressNextClick,
45
+ } from './drag.svelte';
46
+
47
+ let { path = [] }: { path?: number[] } = $props();
48
+
49
+ /**
50
+ * Resolve the current node by walking `layoutStore.root` along the
51
+ * path. $derived tracks the reads so Svelte re-runs this when the
52
+ * layout mutates. If the path becomes invalid mid-mutation (a
53
+ * cleanup pass can collapse nodes out from under a recursive
54
+ * renderer), we render null.
55
+ */
56
+ const node = $derived(nodeAtPath(layoutStore.root, path));
57
+
58
+ /**
59
+ * Build a TabDragController bound to the current tabs node.
60
+ * Rebuilt whenever `node` changes identity (mutation can replace
61
+ * the node at this path with a new one during cleanup).
62
+ */
63
+ function makeController(tabsNode: TabsNode): TabDragController {
64
+ return {
65
+ get isDragging() {
66
+ return dragState.phase === 'dragging';
67
+ },
68
+ onPointerDown(index, event, element) {
69
+ const entry = tabsNode.tabs[index];
70
+ if (!entry) return;
71
+ beginTabDrag(entry.slotId, entry, event, element);
72
+ },
73
+ onStripHover(stripRect, pointerX, pointerY, tabRects) {
74
+ if (pointerY < stripRect.top || pointerY > stripRect.bottom) {
75
+ clearDropTarget((t) => t.kind === 'strip' && t.tabsNode === tabsNode);
76
+ return null;
77
+ }
78
+ let insertIndex = tabRects.length;
79
+ for (let i = 0; i < tabRects.length; i++) {
80
+ const r = tabRects[i];
81
+ const mid = r.left + r.width / 2;
82
+ if (pointerX < mid) {
83
+ insertIndex = i;
84
+ break;
85
+ }
86
+ }
87
+ // Normalize for same-strip reorder so the engine's commit
88
+ // doesn't double-count the removal shift: if the source tab
89
+ // lives in this strip at index < insertIndex, subtract one.
90
+ const source = dragState.source;
91
+ if (source) {
92
+ const srcIdx = tabsNode.tabs.findIndex((t) => t.slotId === source.slotId);
93
+ if (srcIdx >= 0 && srcIdx < insertIndex) insertIndex -= 1;
94
+ }
95
+ setDropTarget({ kind: 'strip', tabsNode, insertIndex });
96
+ return insertIndex;
97
+ },
98
+ onStripLeave() {
99
+ clearDropTarget((t) => t.kind === 'strip' && t.tabsNode === tabsNode);
100
+ },
101
+ };
102
+ }
103
+
104
+ // Narrowing helpers — Svelte templates can't narrow a $derived
105
+ // across block boundaries, so we re-cast inside each branch below.
106
+ // These getters are just there to make the template readable.
107
+ function asSplit(n: LayoutNode) {
108
+ return n.type === 'split' ? n : null;
109
+ }
110
+ function asTabs(n: LayoutNode) {
111
+ return n.type === 'tabs' ? n : null;
112
+ }
113
+ function asSlot(n: LayoutNode) {
114
+ return n.type === 'slot' ? n : null;
115
+ }
116
+
117
+ /** Build per-tab closable flags from reactive pool state. */
118
+ function tabClosable(tabs: import('./types').TabEntry[]): (boolean | undefined)[] {
119
+ return tabs.map((t) => isSlotClosable(t.slotId) || undefined);
120
+ }
121
+
122
+ /** Build per-tab dirty flags for TabbedPanel from live pool state. */
123
+ function tabDirty(tabs: import('./types').TabEntry[]): (boolean | undefined)[] {
124
+ return tabs.map((t) => isSlotDirty(t.slotId) || undefined);
125
+ }
126
+
127
+ /** Handle close button click from TabbedPanel. */
128
+ function handleTabClose(tabs: import('./types').TabEntry[], index: number) {
129
+ const entry = tabs[index];
130
+ if (entry) closeTab(entry.slotId);
131
+ }
132
+
133
+ /** Svelte action: mount a custom empty renderer into the element. */
134
+ function mountEmptyRenderer(node: HTMLElement, renderer: (el: HTMLElement) => void) {
135
+ renderer(node);
136
+ }
137
+
138
+ /**
139
+ * Drop handler for empty persistent tab groups. The whole area acts as
140
+ * a single "insert as first tab" target — no quadrant splits.
141
+ */
142
+ function onEmptyTabsDrop(_e: PointerEvent, tabsNode: import('./types').TabsNode) {
143
+ if (dragState.phase !== 'dragging') return;
144
+ setDropTarget({ kind: 'strip', tabsNode, insertIndex: 0 });
145
+ }
146
+
147
+ function onEmptyTabsLeave() {
148
+ clearDropTarget((t) => t.kind === 'strip' && t.insertIndex === 0);
149
+ }
150
+ </script>
151
+
152
+ {#if node}
153
+ {#if node.type === 'split'}
154
+ {@const split = asSplit(node)!}
155
+ <ResizableSplitter
156
+ direction={split.direction}
157
+ sizes={split.sizes}
158
+ pinned={split.pinned}
159
+ collapsed={split.collapsed}
160
+ count={split.children.length}
161
+ pane={splitPane}
162
+ onResize={(i, v) => (split.sizes[i] = v)}
163
+ onCollapseToggle={(i, v) => {
164
+ if (!split.collapsed) split.collapsed = split.children.map(() => false);
165
+ split.collapsed[i] = v;
166
+ }}
167
+ />
168
+ {#snippet splitPane(i: number)}
169
+ <Self path={[...path, i]} />
170
+ {/snippet}
171
+ {:else if node.type === 'tabs'}
172
+ {@const tabs = asTabs(node)}
173
+ {#if tabs && tabs.tabs.length > 0}
174
+ {@const controller = makeController(tabs)}
175
+ <TabbedPanel
176
+ labels={tabs.tabs.map((t) => t.label)}
177
+ icons={tabs.tabs.map((t) => t.icon)}
178
+ activeTab={tabs.activeTab}
179
+ onActiveChange={(i) => (tabs.activeTab = i)}
180
+ body={tabBody}
181
+ dragController={controller}
182
+ clickGuard={suppressNextClick}
183
+ closable={tabClosable(tabs.tabs)}
184
+ dirty={tabDirty(tabs.tabs)}
185
+ onClose={(i) => handleTabClose(tabs.tabs, i)}
186
+ />
187
+ {#snippet tabBody(i: number)}
188
+ {@const entry = tabs.tabs[i]}
189
+ {#if entry}
190
+ <div class="tab-slot-wrapper">
191
+ <SlotContainer node={{ type: 'slot', slotId: entry.slotId, viewId: entry.viewId }} label={entry.label} />
192
+ <SlotDropZone path={path} />
193
+ </div>
194
+ {/if}
195
+ {/snippet}
196
+ {:else if tabs?.persistent}
197
+ <div class="empty-tabs-placeholder">
198
+ {#if tabs.emptyRenderer}
199
+ <div class="empty-tabs-custom" use:mountEmptyRenderer={tabs.emptyRenderer}></div>
200
+ {:else}
201
+ <div class="empty-tabs-default">
202
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
203
+ <div
204
+ class="empty-tabs-drop"
205
+ onpointermove={(e) => onEmptyTabsDrop(e, tabs)}
206
+ onpointerleave={onEmptyTabsLeave}
207
+ ></div>
208
+ </div>
209
+ {/if}
210
+ </div>
211
+ {/if}
212
+ {:else}
213
+ {@const slot = asSlot(node)!}
214
+ <div class="leaf-slot-wrapper">
215
+ <SlotContainer node={slot} />
216
+ <SlotDropZone path={path} />
217
+ </div>
218
+ {/if}
219
+ {/if}
220
+
221
+ <style>
222
+ .tab-slot-wrapper,
223
+ .leaf-slot-wrapper {
224
+ position: absolute;
225
+ inset: 0;
226
+ min-width: 0;
227
+ min-height: 0;
228
+ }
229
+ .empty-tabs-placeholder {
230
+ width: 100%;
231
+ height: 100%;
232
+ min-width: 0;
233
+ min-height: 0;
234
+ position: relative;
235
+ }
236
+ .empty-tabs-default {
237
+ position: absolute;
238
+ inset: 0;
239
+ display: flex;
240
+ flex-direction: column;
241
+ align-items: center;
242
+ justify-content: center;
243
+ color: var(--shell-fg-muted);
244
+ font-size: 12px;
245
+ background:
246
+ repeating-linear-gradient(
247
+ 45deg,
248
+ var(--shell-bg) 0 10px,
249
+ var(--shell-bg-elevated) 10px 20px
250
+ );
251
+ border: 1px dashed var(--shell-border-strong);
252
+ }
253
+ .empty-tabs-custom {
254
+ position: absolute;
255
+ inset: 0;
256
+ }
257
+ .empty-tabs-drop {
258
+ position: absolute;
259
+ inset: 0;
260
+ pointer-events: auto;
261
+ }
262
+ </style>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ path?: number[];
3
+ };
4
+ declare const LayoutRenderer: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type LayoutRenderer = ReturnType<typeof LayoutRenderer>;
6
+ export default LayoutRenderer;
@@ -0,0 +1,140 @@
1
+ <script lang="ts">
2
+ /*
3
+ * SlotContainer — the leaf of the layout tree and the hand-off point
4
+ * between the framework and shard-contributed views.
5
+ *
6
+ * Phase 6 change: the mounted view no longer lives inside this
7
+ * component's own DOM. Instead, the slot host is owned by the
8
+ * `slotHostPool` module and SlotContainer merely attaches (and later
9
+ * releases) the pooled host to its own wrapper. This is what makes
10
+ * drag-to-reorganize survive: when a tab moves, the old SlotContainer
11
+ * tears down and a new one mounts, but the pooled host (and the view
12
+ * mounted into it) is re-parented to the new wrapper without being
13
+ * destroyed. See slotHostPool.ts for the refcount / deferred-destroy
14
+ * details.
15
+ *
16
+ * Responsibilities:
17
+ * 1. Acquire the pooled host for `node.slotId` on mount and append
18
+ * it to the wrapper.
19
+ * 2. Release the pooled host on unmount. The pool decides whether
20
+ * that's a genuine destroy or the first half of a re-parent.
21
+ * 3. If no factory is registered for the viewId (empty slot or the
22
+ * shard providing it hasn't activated yet), render a placeholder
23
+ * in the wrapper alongside the empty host. A local ResizeObserver
24
+ * feeds the placeholder's dimensions readout.
25
+ *
26
+ * The view's own onResize delivery is NOT SlotContainer's job — the
27
+ * pool owns a ResizeObserver on each host that outlives this component
28
+ * across re-parents. See slotHostPool.ts.
29
+ *
30
+ * Note on the placeholder: the pool creates a host even when there is
31
+ * no factory, so the placeholder is layered *on top* of the empty
32
+ * host. That keeps the acquire/release path uniform — phase 7's
33
+ * "factory registered after layout render" case will just replace the
34
+ * host's contents without rewriting SlotContainer.
35
+ */
36
+
37
+ import type { SlotNode } from './types';
38
+ import { getView } from '../shards/registry';
39
+ import { acquireSlotHost, releaseSlotHost } from './slotHostPool.svelte';
40
+
41
+ let { node, label = '' }: { node: SlotNode; label?: string } = $props();
42
+
43
+ let wrapper: HTMLDivElement | undefined = $state();
44
+ let width = $state(0);
45
+ let height = $state(0);
46
+ // Whether a factory is registered — drives the placeholder. The pool
47
+ // owns the actual mount call; we mirror the registry lookup here just
48
+ // to decide whether to show the "no factory" hint.
49
+ const hasFactory = $derived(node.viewId ? !!getView(node.viewId) : false);
50
+
51
+ $effect(() => {
52
+ if (!wrapper) return;
53
+
54
+ const host = acquireSlotHost(node.slotId, node.viewId, label || node.viewId || node.slotId);
55
+ wrapper.appendChild(host);
56
+
57
+ // Local observer exists only to drive the placeholder's dims text;
58
+ // the view's own onResize is delivered by the pool.
59
+ const ro = new ResizeObserver((entries) => {
60
+ for (const entry of entries) {
61
+ const box = entry.contentRect;
62
+ width = Math.round(box.width);
63
+ height = Math.round(box.height);
64
+ }
65
+ });
66
+ ro.observe(wrapper);
67
+
68
+ return () => {
69
+ ro.disconnect();
70
+ releaseSlotHost(node.slotId);
71
+ };
72
+ });
73
+ </script>
74
+
75
+ <div
76
+ class="slot"
77
+ data-slot-id={node.slotId}
78
+ data-view-id={node.viewId ?? ''}
79
+ bind:this={wrapper}
80
+ >
81
+ {#if !hasFactory}
82
+ <div class="slot-placeholder">
83
+ <div class="slot-id">{node.slotId}</div>
84
+ <div class="slot-meta">
85
+ {#if node.viewId}
86
+ no factory for <code>{node.viewId}</code>
87
+ {:else}
88
+ <em>empty slot</em>
89
+ {/if}
90
+ </div>
91
+ <div class="slot-dims">{width} × {height}</div>
92
+ </div>
93
+ {/if}
94
+ </div>
95
+
96
+ <style>
97
+ .slot {
98
+ position: relative;
99
+ width: 100%;
100
+ height: 100%;
101
+ min-width: 0;
102
+ min-height: 0;
103
+ overflow: hidden;
104
+ }
105
+ .slot-placeholder {
106
+ position: absolute;
107
+ inset: 0;
108
+ display: flex;
109
+ flex-direction: column;
110
+ align-items: center;
111
+ justify-content: center;
112
+ gap: var(--shell-pad-sm);
113
+ color: var(--shell-fg-muted);
114
+ font-size: 12px;
115
+ text-align: center;
116
+ padding: var(--shell-pad-md);
117
+ background:
118
+ repeating-linear-gradient(
119
+ 45deg,
120
+ var(--shell-bg) 0 10px,
121
+ var(--shell-bg-elevated) 10px 20px
122
+ );
123
+ border: 1px dashed var(--shell-border-strong);
124
+ pointer-events: none;
125
+ }
126
+ .slot-id {
127
+ color: var(--shell-fg);
128
+ font-family: var(--shell-font-mono);
129
+ font-size: 13px;
130
+ }
131
+ .slot-meta code {
132
+ font-family: var(--shell-font-mono);
133
+ color: var(--shell-accent);
134
+ }
135
+ .slot-dims {
136
+ font-family: var(--shell-font-mono);
137
+ color: var(--shell-fg-subtle);
138
+ font-size: 11px;
139
+ }
140
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { SlotNode } from './types';
2
+ type $$ComponentProps = {
3
+ node: SlotNode;
4
+ label?: string;
5
+ };
6
+ declare const SlotContainer: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type SlotContainer = ReturnType<typeof SlotContainer>;
8
+ export default SlotContainer;