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,28 @@
1
+ /*
2
+ * Contribution registry — phase 4 stub.
3
+ *
4
+ * Tracks which ViewFactory answers a given viewId. In this phase the
5
+ * registry is a flat module-level Map with no awareness of shard identity;
6
+ * writes come from `activateShard` which additionally remembers which
7
+ * viewIds a given shard registered so they can be torn down in
8
+ * `deactivateShard`.
9
+ *
10
+ * The shape of this registry is deliberately narrow so later phases can
11
+ * expand it without breaking callers:
12
+ * - Resolution by viewId is the only query slots need.
13
+ * - Commands, toolbar items, menus, hotkeys get their own sibling maps
14
+ * (one per contribution kind) when those kinds land.
15
+ */
16
+ const views = new Map();
17
+ export function registerView(viewId, factory) {
18
+ if (views.has(viewId)) {
19
+ throw new Error(`View "${viewId}" is already registered`);
20
+ }
21
+ views.set(viewId, factory);
22
+ }
23
+ export function getView(viewId) {
24
+ return views.get(viewId);
25
+ }
26
+ export function unregisterView(viewId) {
27
+ views.delete(viewId);
28
+ }
@@ -0,0 +1,207 @@
1
+ import type { StateZones } from '../state/zones.svelte';
2
+ import type { ZoneSchema, ZoneManager } from '../state/types';
3
+ import type { DocumentHandle, DocumentHandleOptions } from '../documents/types';
4
+ import type { EnvState } from '../env/types';
5
+ /**
6
+ * The object returned by `ViewFactory.mount`. The framework calls
7
+ * `unmount()` when the slot goes away, and `onResize(w, h)` whenever the
8
+ * slot's container changes size (including splitter drags and window
9
+ * resize). `remountOnMove` is the opt-out from the re-parenting-survival
10
+ * contract (rare, GL-edge-case shards); phase 4 does not exercise it.
11
+ */
12
+ export interface ViewHandle {
13
+ /** Called by the framework when the slot is removed from the layout. Release all resources here. */
14
+ unmount(): void;
15
+ /** Optional callback invoked when the slot's container dimensions change. */
16
+ onResize?(width: number, height: number): void;
17
+ /**
18
+ * When true the framework will unmount and remount this view if its slot
19
+ * moves to a different DOM parent (e.g. a splitter drag). Set to false
20
+ * (or omit) if the view can survive DOM re-parenting without reinitializing.
21
+ */
22
+ remountOnMove?: boolean;
23
+ /**
24
+ * Closability mode for this view instance.
25
+ * - undefined / false: non-closable (no close button rendered).
26
+ * - true: pure close — instant removal, no confirmation.
27
+ * - { canClose() }: guarded close — layout engine awaits the promise;
28
+ * resolve true to allow, false to cancel.
29
+ */
30
+ closable?: boolean | {
31
+ canClose(): Promise<boolean>;
32
+ };
33
+ }
34
+ /**
35
+ * Context passed to `ViewFactory.mount` so the view knows which layout
36
+ * instance it is and can push reactive metadata back to the tab strip.
37
+ */
38
+ export interface MountContext {
39
+ /** Stable identifier for the slot this view is mounted into. */
40
+ slotId: string;
41
+ /** The view id that was used to look up this factory. */
42
+ viewId: string;
43
+ /** Initial label for the tab; may be updated by the view via future API. */
44
+ label: string;
45
+ /**
46
+ * Push dirty-state to the tab strip. The framework renders a dirty
47
+ * indicator (filled dot) on the tab when true, clears it when false.
48
+ * Call this whenever the view's save-state changes.
49
+ */
50
+ setDirty(dirty: boolean): void;
51
+ }
52
+ /**
53
+ * The shard-side adapter that knows how to bring a view to life inside a
54
+ * given HTMLElement. The container is owned by the framework (the slot);
55
+ * the factory writes into it and returns a ViewHandle.
56
+ */
57
+ export interface ViewFactory {
58
+ /**
59
+ * Mount the view into `container`. The container element is owned and sized
60
+ * by the framework; the factory should not modify its dimensions or position.
61
+ * Returns a `ViewHandle` the framework uses to communicate with the view.
62
+ */
63
+ mount(container: HTMLElement, context: MountContext): ViewHandle;
64
+ }
65
+ /**
66
+ * Static description of a view a shard provides. Lives inside
67
+ * `ShardManifest.views` so apps and tooling can enumerate a shard's views
68
+ * without running `activate()`. Phase 8 only uses `id` and `label`; `icon`
69
+ * is reserved and not rendered yet.
70
+ */
71
+ export interface ViewDeclaration {
72
+ /** Unique view id within this shard. Used to look up the factory at mount time. */
73
+ id: string;
74
+ /** Human-readable label used in tab strips and launchers. */
75
+ label: string;
76
+ /** Optional icon hint (reserved; not yet rendered in phase 8). */
77
+ icon?: string;
78
+ }
79
+ /**
80
+ * Static description of a shard. Declared once and read by the framework
81
+ * before `activate` runs, so the shell can enumerate a shard's capabilities
82
+ * without executing any shard code.
83
+ */
84
+ export interface ShardManifest {
85
+ /** Unique shard identifier. Used as the state namespace and view id prefix. */
86
+ id: string;
87
+ /** Human-readable display name. */
88
+ label: string;
89
+ /** Semver version string for the shard package. */
90
+ version: string;
91
+ /**
92
+ * Static list of the view ids this shard provides. Every id listed here
93
+ * must be backed by a `ctx.registerView(id, factory)` call from within
94
+ * `activate()`; the framework verifies this after `activate` returns and
95
+ * throws on any missing factory. Phase 8 adds this field; shards that
96
+ * predate phase 8 must be updated to declare their views here.
97
+ */
98
+ views: ViewDeclaration[];
99
+ /**
100
+ * Optional filename of a server-side bundle for this shard. When present,
101
+ * sh3-server loads the bundle at boot and mounts its routes at
102
+ * `/api/<shard-id>/`. The server bundle runs in Node with full access.
103
+ * Only relevant for shards installed via the package store; framework-
104
+ * shipped shards do not use this field.
105
+ */
106
+ serverBundle?: string;
107
+ /**
108
+ * Optional permissions this shard requests beyond the default sandbox.
109
+ * Declared in the manifest and surfaced to the user at install time.
110
+ * Currently recognized: `'state:manage'` — cross-shard zone access.
111
+ */
112
+ permissions?: string[];
113
+ }
114
+ /**
115
+ * Handed to `shard.activate`. The shard uses it to declare state and
116
+ * register contributions. `state` is pre-bound to the shard's id so the
117
+ * shard never has to pass its own id through — the framework guarantees
118
+ * isolation by construction.
119
+ */
120
+ export interface ShardContext {
121
+ /**
122
+ * Declare the state zones this shard uses and receive a live reactive
123
+ * object. The shard id is baked in — shards never pass their own id.
124
+ * Persistent zones (`workspace`, `user`) are hydrated before the call
125
+ * returns.
126
+ *
127
+ * @param schema - Zone names mapped to their default values.
128
+ */
129
+ state<T extends ZoneSchema>(schema: T): StateZones<T>;
130
+ /**
131
+ * Register a view factory for a view id declared in the shard manifest.
132
+ * Must be called for every id listed in `manifest.views` during `activate`.
133
+ *
134
+ * @param viewId - Must match an entry in `manifest.views`.
135
+ * @param factory - The adapter that mounts the view into a container element.
136
+ */
137
+ registerView(viewId: string, factory: ViewFactory): void;
138
+ /** Obtain a file-oriented document handle scoped to this shard. */
139
+ documents(options: DocumentHandleOptions): DocumentHandle;
140
+ /**
141
+ * Declare environment state for this shard and receive a hydrated snapshot.
142
+ * Env state is server-authoritative, fetched once at activation, and
143
+ * shallow-merged with defaults (new fields get defaults even if the server
144
+ * has an older entry). Read-only for non-admin sessions.
145
+ *
146
+ * @param defaults - Default values for each env state field.
147
+ * @returns A reactive proxy of the env state.
148
+ */
149
+ env<T extends Record<string, unknown>>(defaults: T): EnvState<T>;
150
+ /**
151
+ * Update this shard's environment state on the server. Merges the patch
152
+ * into the current env state, writes to the server, and updates the local
153
+ * reactive proxy. Throws if the session is not admin-elevated.
154
+ *
155
+ * @param patch - Partial env state to merge.
156
+ */
157
+ envUpdate<T extends Record<string, unknown>>(patch: Partial<T>): Promise<void>;
158
+ /** Whether the current session has admin privileges. */
159
+ isAdmin: boolean;
160
+ /**
161
+ * Cross-shard zone management API. Only present when the shard's
162
+ * manifest declares the `'state:manage'` permission. Check with
163
+ * `if (ctx.zones)` before use.
164
+ */
165
+ zones?: ZoneManager;
166
+ }
167
+ /**
168
+ * A shard module. Shards are the fundamental unit of contribution in SH3.
169
+ * Each shard activates once, receives a `ShardContext`, and registers views
170
+ * and other capabilities that the shell and apps can use. Shard lifecycle is
171
+ * separate from view lifecycle — a shard may be active with no views visible.
172
+ */
173
+ export interface Shard {
174
+ /** Static description of this shard's identity and declared contributions. */
175
+ manifest: ShardManifest;
176
+ /**
177
+ * Run once when the shard is activated. Use `ctx` to declare state zones,
178
+ * register view factories, and obtain document handles. Must register a
179
+ * factory for every view id declared in `manifest.views`.
180
+ */
181
+ activate(ctx: ShardContext): void | Promise<void>;
182
+ /**
183
+ * Optional self-starting hook. A shard that defines `autostart` is
184
+ * eagerly activated by the framework at boot (right after the register
185
+ * pass finishes) instead of waiting for an app to require it. `activate`
186
+ * runs first; `autostart` runs immediately after and may take imperative
187
+ * action — docking its own views into the active layout, opening a
188
+ * modal, subscribing to framework state, etc. The `__sh3core__` pseudo-
189
+ * shard uses this with a no-op body so its activation path is uniform
190
+ * with other self-starting shards. Diagnostic-style shards use it to
191
+ * do real work.
192
+ */
193
+ autostart?(ctx: ShardContext): void | Promise<void>;
194
+ /** Optional cleanup hook called when the shard is deactivated. Release timers, subscriptions, and external resources here. */
195
+ deactivate?(): void | Promise<void>;
196
+ /**
197
+ * Called when the owning app is suspended (Home button). The shard
198
+ * remains active; its views and state are preserved. Return `false`
199
+ * (sync or async) to cancel the navigation.
200
+ */
201
+ suspend?(): void | false | Promise<void | false>;
202
+ /**
203
+ * Called when the owning app resumes from Home. Receives the same
204
+ * `ShardContext` that `activate` received.
205
+ */
206
+ resume?(ctx: ShardContext): void | Promise<void>;
207
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Shard contract — minimum viable draft for phase 4.
3
+ *
4
+ * A shard is a self-contained module that contributes capabilities (views,
5
+ * commands, services, …) to the shell. See docs/design/shards.md for the
6
+ * full design; phase 4 implements only the pieces needed to mount a view
7
+ * into a layout slot:
8
+ *
9
+ * - A shard declares a manifest.
10
+ * - `activate(ctx)` runs once, receives a ShardContext, and registers
11
+ * whichever contributions it wants the shell to know about.
12
+ * - A ViewFactory knows how to mount a view into a raw HTMLElement and
13
+ * return a handle the framework uses to unmount / notify of resizes.
14
+ *
15
+ * Deferred to later phases: bus scoping, command/toolbar/menu/hotkey
16
+ * registration, modal provider contributions, background services, lazy
17
+ * activation events. They'll slot into `ShardContext` as new `register*`
18
+ * methods without disturbing the phase-4 shape.
19
+ */
20
+ export {};
@@ -0,0 +1,133 @@
1
+ <script lang="ts">
2
+ import type { SessionClient } from './session-client.svelte';
3
+
4
+ interface Props {
5
+ cwd: string;
6
+ locked: boolean; // true while a process is running
7
+ history: string[]; // persisted history, newest last
8
+ session: SessionClient;
9
+ onSubmit: (line: string) => void; // called with the raw entered line
10
+ }
11
+ let { cwd, locked, history, session, onSubmit }: Props = $props();
12
+
13
+ let draft = $state('');
14
+ let historyIndex = $state<number | null>(null); // null = live draft
15
+ let savedDraft = $state(''); // restored when user returns from history
16
+
17
+ let input: HTMLInputElement | null = $state(null);
18
+
19
+ function submit() {
20
+ if (locked) return;
21
+ const line = draft;
22
+ if (!line.trim()) return;
23
+ draft = '';
24
+ historyIndex = null;
25
+ onSubmit(line);
26
+ }
27
+
28
+ function navHistoryUp() {
29
+ if (history.length === 0) return;
30
+ if (historyIndex === null) {
31
+ savedDraft = draft;
32
+ historyIndex = history.length - 1;
33
+ } else if (historyIndex > 0) {
34
+ historyIndex--;
35
+ }
36
+ draft = history[historyIndex] ?? '';
37
+ }
38
+
39
+ function navHistoryDown() {
40
+ if (historyIndex === null) return;
41
+ if (historyIndex < history.length - 1) {
42
+ historyIndex++;
43
+ draft = history[historyIndex];
44
+ } else {
45
+ historyIndex = null;
46
+ draft = savedDraft;
47
+ }
48
+ }
49
+
50
+ function onKeyDown(e: KeyboardEvent) {
51
+ if (locked) {
52
+ if (e.ctrlKey && e.key === 'c') {
53
+ e.preventDefault();
54
+ session.send({ t: 'signal', sig: 'SIGINT' });
55
+ return;
56
+ }
57
+ if (e.ctrlKey && e.key === 'd') {
58
+ e.preventDefault();
59
+ session.send({ t: 'signal', sig: 'EOF' });
60
+ return;
61
+ }
62
+ // Drop all other input while locked
63
+ e.preventDefault();
64
+ return;
65
+ }
66
+
67
+ if (e.key === 'Enter') {
68
+ e.preventDefault();
69
+ submit();
70
+ } else if (e.key === 'ArrowUp') {
71
+ e.preventDefault();
72
+ navHistoryUp();
73
+ } else if (e.key === 'ArrowDown') {
74
+ e.preventDefault();
75
+ navHistoryDown();
76
+ } else if (e.ctrlKey && e.key === 'c') {
77
+ e.preventDefault();
78
+ draft = '';
79
+ historyIndex = null;
80
+ } else if (e.ctrlKey && e.key === 'l') {
81
+ e.preventDefault();
82
+ draft = 'clear';
83
+ submit();
84
+ }
85
+ }
86
+
87
+ // Re-focus the input when the locked state flips to false
88
+ $effect(() => {
89
+ if (!locked && input) {
90
+ input.focus();
91
+ }
92
+ });
93
+ </script>
94
+
95
+ <div class="shell-input" class:locked>
96
+ <span class="shell-input-cwd">{cwd}</span>
97
+ <span class="shell-input-arrow">❯</span>
98
+ <input
99
+ bind:this={input}
100
+ bind:value={draft}
101
+ type="text"
102
+ disabled={locked}
103
+ onkeydown={onKeyDown}
104
+ spellcheck="false"
105
+ autocomplete="off"
106
+ autocapitalize="off"
107
+ class="shell-input-field"
108
+ />
109
+ </div>
110
+
111
+ <style>
112
+ .shell-input {
113
+ display: flex;
114
+ gap: 8px;
115
+ padding: 4px 8px;
116
+ border-top: 1px solid var(--shell-border, #333);
117
+ font-family: var(--shell-font-mono, monospace);
118
+ }
119
+ .shell-input-cwd { color: var(--shell-fg-muted, #888); }
120
+ .shell-input-arrow { color: var(--shell-accent, #6cf); }
121
+ .shell-input-field {
122
+ flex: 1 1 auto;
123
+ background: transparent;
124
+ border: 0;
125
+ outline: 0;
126
+ color: var(--shell-fg, #ddd);
127
+ font: inherit;
128
+ }
129
+ .shell-input.locked .shell-input-field {
130
+ opacity: 0.5;
131
+ cursor: default;
132
+ }
133
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { SessionClient } from './session-client.svelte';
2
+ interface Props {
3
+ cwd: string;
4
+ locked: boolean;
5
+ history: string[];
6
+ session: SessionClient;
7
+ onSubmit: (line: string) => void;
8
+ }
9
+ declare const InputLine: import("svelte").Component<Props, {}, "">;
10
+ type InputLine = ReturnType<typeof InputLine>;
11
+ export default InputLine;
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { Scrollback } from './scrollback.svelte';
3
+ import TextEntry from './entries/TextEntry.svelte';
4
+ import PromptEntry from './entries/PromptEntry.svelte';
5
+ import StatusEntry from './entries/StatusEntry.svelte';
6
+ import RichEntry from './entries/RichEntry.svelte';
7
+
8
+ interface Props {
9
+ scrollback: Scrollback;
10
+ }
11
+ let { scrollback }: Props = $props();
12
+
13
+ let container: HTMLDivElement | null = $state(null);
14
+
15
+ // Auto-scroll to bottom on new entries
16
+ $effect(() => {
17
+ // Depend on entries length so the effect re-runs
18
+ const _len = scrollback.entries.length;
19
+ void _len;
20
+ if (container) {
21
+ container.scrollTop = container.scrollHeight;
22
+ }
23
+ });
24
+ </script>
25
+
26
+ <div class="shell-scrollback" bind:this={container}>
27
+ {#each scrollback.entries as entry (entry.id)}
28
+ {#if entry.kind === 'text'}
29
+ <TextEntry stream={entry.stream} chunks={entry.chunks} />
30
+ {:else if entry.kind === 'prompt'}
31
+ <PromptEntry cwd={entry.cwd} line={entry.line} />
32
+ {:else if entry.kind === 'status'}
33
+ <StatusEntry text={entry.text} level={entry.level} />
34
+ {:else if entry.kind === 'rich'}
35
+ <RichEntry component={entry.component} componentProps={entry.props} />
36
+ {/if}
37
+ {/each}
38
+ </div>
39
+
40
+ <style>
41
+ .shell-scrollback {
42
+ flex: 1 1 auto;
43
+ overflow-y: auto;
44
+ background: var(--shell-bg, #111);
45
+ color: var(--shell-fg, #ddd);
46
+ }
47
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { Scrollback } from './scrollback.svelte';
2
+ interface Props {
3
+ scrollback: Scrollback;
4
+ }
5
+ declare const ScrollbackView: import("svelte").Component<Props, {}, "">;
6
+ type ScrollbackView = ReturnType<typeof ScrollbackView>;
7
+ export default ScrollbackView;
@@ -0,0 +1,122 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, untrack } from 'svelte';
3
+ import { Scrollback } from './scrollback.svelte';
4
+ import ScrollbackView from './ScrollbackView.svelte';
5
+ import InputLine from './InputLine.svelte';
6
+ import { SessionClient } from './session-client.svelte';
7
+ import { VerbRegistry, type ShellApi } from './registry';
8
+ import { registerV1Verbs } from './verbs';
9
+ import type { ServerMessage } from './protocol';
10
+
11
+ interface Props {
12
+ shell: ShellApi;
13
+ wsUrl: string;
14
+ }
15
+ let { shell, wsUrl }: Props = $props();
16
+
17
+ const scrollback = new Scrollback();
18
+ // wsUrl is a prop read at construction only. untrack prevents Svelte 5's
19
+ // "referenced outside a closure" warning; the URL never changes at runtime.
20
+ const session = untrack(() => new SessionClient(wsUrl));
21
+ const registry = new VerbRegistry();
22
+ registerV1Verbs(registry);
23
+
24
+ let locked = $state(false);
25
+
26
+ async function dispatch(line: string): Promise<void> {
27
+ const resolution = registry.resolve(line);
28
+ if (resolution.kind === 'local') {
29
+ // Log locally-dispatched verbs for shared history
30
+ session.send({ t: 'history-log', line });
31
+ scrollback.push({
32
+ kind: 'prompt',
33
+ cwd: session.cwd,
34
+ line,
35
+ ts: Date.now(),
36
+ });
37
+ try {
38
+ await resolution.verb.run({
39
+ shell,
40
+ scrollback,
41
+ session,
42
+ cwd: session.cwd,
43
+ dispatch,
44
+ }, resolution.args);
45
+ } catch (err) {
46
+ scrollback.push({
47
+ kind: 'status',
48
+ text: `shell: verb ${resolution.verb.name} threw — ${(err as Error).message}`,
49
+ level: 'error',
50
+ ts: Date.now(),
51
+ });
52
+ }
53
+ } else {
54
+ // Forward to server
55
+ session.send({ t: 'submit', line: resolution.line });
56
+ }
57
+ }
58
+
59
+ function handleServerMessage(msg: ServerMessage) {
60
+ if (msg.t !== 'event') return;
61
+ const e = msg.event;
62
+ switch (e.kind) {
63
+ case 'prompt':
64
+ scrollback.push({ kind: 'prompt', cwd: e.cwd, line: e.line, ts: e.ts });
65
+ locked = true;
66
+ break;
67
+ case 'stdout':
68
+ scrollback.push({ kind: 'text', stream: 'stdout', chunks: [e.data], ts: e.ts });
69
+ break;
70
+ case 'stderr':
71
+ scrollback.push({ kind: 'text', stream: 'stderr', chunks: [e.data], ts: e.ts });
72
+ break;
73
+ case 'exit':
74
+ scrollback.push({
75
+ kind: 'status',
76
+ text: e.signal
77
+ ? `shell: process exited (${e.signal})`
78
+ : `shell: process exited (${e.code ?? 0})`,
79
+ level: e.code === 0 || e.code === null ? 'info' : 'error',
80
+ ts: e.ts,
81
+ });
82
+ locked = false;
83
+ break;
84
+ case 'status':
85
+ scrollback.push({ kind: 'status', text: e.text, level: e.level, ts: e.ts });
86
+ break;
87
+ }
88
+ }
89
+
90
+ let unsub: (() => void) | null = null;
91
+
92
+ onMount(() => {
93
+ unsub = session.onMessage(handleServerMessage);
94
+ session.connect();
95
+ });
96
+
97
+ onDestroy(() => {
98
+ unsub?.();
99
+ session.close();
100
+ });
101
+ </script>
102
+
103
+ <div class="shell-terminal">
104
+ <ScrollbackView {scrollback} />
105
+ <InputLine
106
+ cwd={session.cwd}
107
+ {locked}
108
+ history={session.history}
109
+ {session}
110
+ onSubmit={dispatch}
111
+ />
112
+ </div>
113
+
114
+ <style>
115
+ .shell-terminal {
116
+ display: flex;
117
+ flex-direction: column;
118
+ height: 100%;
119
+ background: var(--shell-bg, #111);
120
+ color: var(--shell-fg, #ddd);
121
+ }
122
+ </style>
@@ -0,0 +1,8 @@
1
+ import { type ShellApi } from './registry';
2
+ interface Props {
3
+ shell: ShellApi;
4
+ wsUrl: string;
5
+ }
6
+ declare const Terminal: import("svelte").Component<Props, {}, "">;
7
+ type Terminal = ReturnType<typeof Terminal>;
8
+ export default Terminal;
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ cwd: string;
4
+ line: string;
5
+ }
6
+ let { cwd, line }: Props = $props();
7
+ </script>
8
+
9
+ <div class="shell-prompt">
10
+ <span class="shell-prompt-cwd">{cwd}</span>
11
+ <span class="shell-prompt-arrow">❯</span>
12
+ <span class="shell-prompt-line">{line}</span>
13
+ </div>
14
+
15
+ <style>
16
+ .shell-prompt {
17
+ padding: 4px 8px 0 8px;
18
+ font-family: var(--shell-font-mono, monospace);
19
+ display: flex;
20
+ gap: 8px;
21
+ }
22
+ .shell-prompt-cwd { color: var(--shell-fg-muted, #888); }
23
+ .shell-prompt-arrow { color: var(--shell-accent, #6cf); }
24
+ .shell-prompt-line { color: var(--shell-fg, #ddd); }
25
+ </style>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ cwd: string;
3
+ line: string;
4
+ }
5
+ declare const PromptEntry: import("svelte").Component<Props, {}, "">;
6
+ type PromptEntry = ReturnType<typeof PromptEntry>;
7
+ export default PromptEntry;
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import type { Component } from 'svelte';
3
+
4
+ interface Props {
5
+ component: Component;
6
+ componentProps: Record<string, unknown>;
7
+ }
8
+ let { component: C, componentProps }: Props = $props();
9
+ </script>
10
+
11
+ <div class="shell-rich">
12
+ <C {...componentProps} />
13
+ </div>
14
+
15
+ <style>
16
+ .shell-rich {
17
+ padding: 4px 8px;
18
+ }
19
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { Component } from 'svelte';
2
+ interface Props {
3
+ component: Component;
4
+ componentProps: Record<string, unknown>;
5
+ }
6
+ declare const RichEntry: Component<Props, {}, "">;
7
+ type RichEntry = ReturnType<typeof RichEntry>;
8
+ export default RichEntry;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ text: string;
4
+ level: 'info' | 'warn' | 'error';
5
+ }
6
+ let { text, level }: Props = $props();
7
+ </script>
8
+
9
+ <div class="shell-status" class:info={level === 'info'} class:warn={level === 'warn'} class:error={level === 'error'}>
10
+ {text}
11
+ </div>
12
+
13
+ <style>
14
+ .shell-status {
15
+ padding: 2px 8px;
16
+ font-family: var(--shell-font-mono, monospace);
17
+ font-style: italic;
18
+ }
19
+ .shell-status.info { color: var(--shell-fg-muted, #888); }
20
+ .shell-status.warn { color: var(--shell-fg-warn, #fc6); }
21
+ .shell-status.error { color: var(--shell-fg-error, #f88); }
22
+ </style>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ text: string;
3
+ level: 'info' | 'warn' | 'error';
4
+ }
5
+ declare const StatusEntry: import("svelte").Component<Props, {}, "">;
6
+ type StatusEntry = ReturnType<typeof StatusEntry>;
7
+ export default StatusEntry;