sh3-core 0.6.0 → 0.7.1

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 (92) hide show
  1. package/dist/Shell.svelte +20 -14
  2. package/dist/api.d.ts +7 -3
  3. package/dist/api.js +1 -0
  4. package/dist/app/admin/adminApp.js +2 -1
  5. package/dist/app/admin/adminShard.svelte.js +2 -1
  6. package/dist/app/store/StoreView.svelte +11 -5
  7. package/dist/app/store/storeApp.js +2 -1
  8. package/dist/app/store/storeShard.svelte.js +14 -4
  9. package/dist/app/store/verbs.d.ts +4 -0
  10. package/dist/app/store/verbs.js +220 -0
  11. package/dist/apps/terminal/manifest.js +2 -1
  12. package/dist/apps/types.d.ts +28 -7
  13. package/dist/build.d.ts +5 -2
  14. package/dist/build.js +21 -10
  15. package/dist/env/client.d.ts +10 -2
  16. package/dist/env/client.js +13 -2
  17. package/dist/layout/LayoutRenderer.svelte +21 -9
  18. package/dist/layout/LayoutRenderer.svelte.d.ts +2 -0
  19. package/dist/layout/SlotDropZone.svelte +4 -1
  20. package/dist/layout/SlotDropZone.svelte.d.ts +2 -0
  21. package/dist/layout/drag.svelte.d.ts +5 -2
  22. package/dist/layout/drag.svelte.js +43 -11
  23. package/dist/layout/floats.d.ts +35 -0
  24. package/dist/layout/floats.js +73 -0
  25. package/dist/layout/floats.test.d.ts +1 -0
  26. package/dist/layout/floats.test.js +114 -0
  27. package/dist/layout/inspection.d.ts +2 -2
  28. package/dist/layout/inspection.js +6 -6
  29. package/dist/layout/ops.d.ts +14 -1
  30. package/dist/layout/ops.js +17 -0
  31. package/dist/layout/ops.test.d.ts +1 -0
  32. package/dist/layout/ops.test.js +36 -0
  33. package/dist/layout/presets.d.ts +2 -0
  34. package/dist/layout/presets.js +49 -0
  35. package/dist/layout/presets.test.d.ts +1 -0
  36. package/dist/layout/presets.test.js +71 -0
  37. package/dist/layout/store.svelte.d.ts +17 -13
  38. package/dist/layout/store.svelte.js +98 -36
  39. package/dist/layout/tree-walk.d.ts +12 -1
  40. package/dist/layout/tree-walk.js +13 -0
  41. package/dist/layout/tree-walk.test.d.ts +1 -0
  42. package/dist/layout/tree-walk.test.js +41 -0
  43. package/dist/layout/types.d.ts +96 -6
  44. package/dist/layout/types.js +1 -1
  45. package/dist/overlays/FloatFrame.svelte +142 -0
  46. package/dist/overlays/FloatFrame.svelte.d.ts +7 -0
  47. package/dist/overlays/FloatLayer.svelte +28 -0
  48. package/dist/overlays/FloatLayer.svelte.d.ts +3 -0
  49. package/dist/overlays/float.d.ts +29 -0
  50. package/dist/overlays/float.js +119 -0
  51. package/dist/overlays/float.test.d.ts +1 -0
  52. package/dist/overlays/float.test.js +37 -0
  53. package/dist/overlays/presets.d.ts +21 -0
  54. package/dist/overlays/presets.js +63 -0
  55. package/dist/overlays/presets.test.d.ts +1 -0
  56. package/dist/overlays/presets.test.js +40 -0
  57. package/dist/registry/client.d.ts +14 -0
  58. package/dist/registry/client.js +37 -0
  59. package/dist/registry/client.test.d.ts +1 -0
  60. package/dist/registry/client.test.js +54 -0
  61. package/dist/registry/installer.js +18 -5
  62. package/dist/registry/schema.js +5 -0
  63. package/dist/registry/types.d.ts +9 -0
  64. package/dist/shards/activate.svelte.js +9 -2
  65. package/dist/shards/registry.d.ts +5 -0
  66. package/dist/shards/registry.js +19 -3
  67. package/dist/shards/registry.test.d.ts +1 -0
  68. package/dist/shards/registry.test.js +62 -0
  69. package/dist/shards/types.d.ts +36 -4
  70. package/dist/shell-shard/Terminal.svelte +17 -12
  71. package/dist/shell-shard/manifest.js +2 -1
  72. package/dist/shell-shard/registry.d.ts +2 -64
  73. package/dist/shell-shard/registry.js +9 -17
  74. package/dist/shell-shard/shellShard.svelte.js +4 -1
  75. package/dist/shell-shard/verbs/apps.d.ts +1 -1
  76. package/dist/shell-shard/verbs/clear.d.ts +1 -1
  77. package/dist/shell-shard/verbs/help.d.ts +2 -2
  78. package/dist/shell-shard/verbs/help.js +3 -2
  79. package/dist/shell-shard/verbs/history.d.ts +1 -1
  80. package/dist/shell-shard/verbs/index.d.ts +2 -2
  81. package/dist/shell-shard/verbs/index.js +18 -18
  82. package/dist/shell-shard/verbs/session.d.ts +1 -1
  83. package/dist/shell-shard/verbs/shards.d.ts +1 -1
  84. package/dist/shell-shard/verbs/views.d.ts +1 -1
  85. package/dist/shell-shard/verbs/zones.d.ts +1 -1
  86. package/dist/shellRuntime.svelte.d.ts +6 -0
  87. package/dist/shellRuntime.svelte.js +4 -0
  88. package/dist/verbs/types.d.ts +62 -0
  89. package/dist/verbs/types.js +8 -0
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/package.json +6 -3
package/dist/Shell.svelte CHANGED
@@ -16,11 +16,12 @@
16
16
  import './primitives/base.css';
17
17
  import LayoutRenderer from './layout/LayoutRenderer.svelte';
18
18
  import DragPreview from './layout/DragPreview.svelte';
19
+ import FloatLayer from './overlays/FloatLayer.svelte';
19
20
  import type { OverlayLayer } from './overlays/types';
20
21
  import { registerLayerRoot, unregisterLayerRoot } from './overlays/roots';
22
+ import { bindFloatStore, unbindFloatStore, floatManager } from './overlays/float';
21
23
  import { returnToHome, isAdmin } from './api';
22
- import { getActiveRoot } from './layout/store.svelte';
23
- import { dockIntoActiveLayout } from './layout/inspection';
24
+ import { getActiveRoot, layoutStore } from './layout/store.svelte';
24
25
  import { isAuthenticated, isLocalOwner, getUser, logout } from './auth/index';
25
26
  import iconsUrl from './assets/icons.svg';
26
27
  import GuestBanner from './auth/GuestBanner.svelte';
@@ -59,12 +60,10 @@
59
60
  };
60
61
  });
61
62
 
62
- // The AZERTY `²` key (top-left, below Escape) docks the terminal view
63
- // into the currently-rendered layout whichever app is active, or the
64
- // home root if none. Admin-only because the shell shard is admin-gated.
65
- // Provisional ergonomics shortcut; a real hotkey contribution API is
66
- // DF1 in the roadmap, and dedicated floating-window placement is
67
- // SH9 / DF3.
63
+ // The AZERTY `²` key (top-left, below Escape) opens the terminal view
64
+ // in a floating panel (DF3 float runtime). Admin-only because the shell
65
+ // shard is admin-gated. Provisional ergonomics shortcut; a real hotkey
66
+ // contribution API is DF1 in the roadmap.
68
67
  //
69
68
  // Bare key (no modifier) so it must be suppressed while the user is
70
69
  // typing into an input/textarea/contenteditable, or the shortcut would
@@ -83,15 +82,20 @@
83
82
  ) return;
84
83
  }
85
84
  e.preventDefault();
86
- dockIntoActiveLayout({
87
- slotId: `shell.terminal.${Date.now()}`,
88
- viewId: 'shell:terminal',
89
- label: 'Shell',
90
- });
85
+ floatManager.open('shell:terminal', { title: 'Shell' });
91
86
  }
92
87
  window.addEventListener('keydown', onKeyDown);
93
88
  return () => window.removeEventListener('keydown', onKeyDown);
94
89
  });
90
+
91
+ $effect(() => {
92
+ const tree = layoutStore.tree;
93
+ bindFloatStore(tree.floats, () => ({
94
+ w: window.innerWidth,
95
+ h: window.innerHeight,
96
+ }));
97
+ return () => unbindFloatStore();
98
+ });
95
99
  </script>
96
100
 
97
101
  <div class="shell">
@@ -152,7 +156,9 @@
152
156
  style="z-index: var(--shell-z-layer-{layer});"
153
157
  bind:this={overlayRoots[name]}
154
158
  >
155
- {#if name === 'drag-preview'}
159
+ {#if name === 'floating'}
160
+ <FloatLayer />
161
+ {:else if name === 'drag-preview'}
156
162
  <DragPreview />
157
163
  {/if}
158
164
  </div>
package/dist/api.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  export { shell } from './shellRuntime.svelte';
2
2
  export type { Shell } from './shellRuntime.svelte';
3
- export type { Shard, ShardManifest, ShardContext, ViewDeclaration, ViewFactory, ViewHandle, MountContext, } from './shards/types';
4
- export type { LayoutNode, SplitNode, TabsNode, SlotNode, TabEntry, SplitDirection, SizeMode, } from './layout/types';
3
+ export type { Shard, ShardManifest, SourceShard, SourceShardManifest, ShardContext, ViewDeclaration, ViewFactory, ViewHandle, MountContext, } from './shards/types';
4
+ export type { LayoutNode, SplitNode, TabsNode, SlotNode, TabEntry, SplitDirection, SizeMode, LayoutTree, FloatEntry, LayoutPreset, CanonicalPreset, } from './layout/types';
5
+ export type { FloatManager, FloatOptions } from './overlays/float';
6
+ export type { PresetManager } from './overlays/presets';
5
7
  export type { ZoneSchema, ZoneName, ZoneManager } from './state/types';
6
8
  export { PERMISSION_STATE_MANAGE } from './state/types';
7
9
  export type { StateZones } from './state/zones.svelte';
8
10
  export type { EnvState } from './env/types';
9
- export type { App, AppManifest, AppContext } from './apps/types';
11
+ export type { App, AppManifest, SourceApp, SourceAppManifest, AppContext, } from './apps/types';
10
12
  export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
11
13
  export { launchApp, returnToHome } from './apps/lifecycle';
12
14
  export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
@@ -24,5 +26,7 @@ export declare const capabilities: {
24
26
  readonly hotInstall: boolean;
25
27
  };
26
28
  export type { ServerShard, ServerShardContext } from './server-shard/types';
29
+ export type { Verb } from './verbs/types';
30
+ export { listVerbs } from './shards/registry';
27
31
  export { VERSION } from './version';
28
32
  export { setTokenOverrides, clearTokenOverrides, getTokenOverrides, } from './theme';
package/dist/api.js CHANGED
@@ -44,6 +44,7 @@ export const capabilities = {
44
44
  /** Whether this target supports hot-installing packages via dynamic import from blob URL. */
45
45
  hotInstall: typeof Blob !== 'undefined' && typeof URL.createObjectURL === 'function',
46
46
  };
47
+ export { listVerbs } from './shards/registry';
47
48
  // Package version.
48
49
  export { VERSION } from './version';
49
50
  // Theme token override API (shell-level theming support).
@@ -3,11 +3,12 @@
3
3
  * Framework-shipped: registered in host.ts during bootstrap.
4
4
  * Admin-gated via manifest flag (ADR-011).
5
5
  */
6
+ import { VERSION } from '../../version';
6
7
  export const adminApp = {
7
8
  manifest: {
8
9
  id: 'sh3-admin-app',
9
10
  label: 'Admin',
10
- version: '0.1.0',
11
+ version: VERSION,
11
12
  requiredShards: ['sh3-admin'],
12
13
  layoutVersion: 1,
13
14
  admin: true,
@@ -15,13 +15,14 @@ import UsersView from './UsersView.svelte';
15
15
  import AuthSettingsView from './AuthSettingsView.svelte';
16
16
  import SystemView from './SystemView.svelte';
17
17
  import ApiKeysView from './ApiKeysView.svelte';
18
+ import { VERSION } from '../../version';
18
19
  /** Module-level server URL, set during activate. */
19
20
  export let adminServerUrl = '';
20
21
  export const adminShard = {
21
22
  manifest: {
22
23
  id: 'sh3-admin',
23
24
  label: 'Admin',
24
- version: '0.1.0',
25
+ version: VERSION,
25
26
  views: [
26
27
  { id: 'sh3-admin:users', label: 'Users' },
27
28
  { id: 'sh3-admin:auth', label: 'Auth Settings' },
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { storeContext } from './storeShard.svelte';
10
- import { fetchBundle, buildPackageMeta } from '../../registry/client';
10
+ import { fetchBundle, fetchServerBundle, buildPackageMeta } from '../../registry/client';
11
11
  import { installPackage } from '../../registry/installer';
12
12
  import { serverInstallPackage } from '../../env/client';
13
13
  import { contract } from '../../contract';
@@ -100,11 +100,17 @@
100
100
  installError = null;
101
101
 
102
102
  try {
103
- // 1. Fetch and integrity-verify the bundle from the registry.
103
+ // 1. Fetch and integrity-verify the client bundle from the registry.
104
104
  const bundle = await fetchBundle(pkg.latest, pkg.sourceRegistry);
105
105
  const meta = buildPackageMeta(pkg, pkg.latest);
106
106
 
107
- // 2. Upload to server for persistent storage.
107
+ // 2. Fetch the server bundle if the package declares one.
108
+ let serverBundle: ArrayBuffer | undefined;
109
+ if (pkg.latest.serverBundleUrl) {
110
+ serverBundle = await fetchServerBundle(pkg.latest, pkg.sourceRegistry);
111
+ }
112
+
113
+ // 3. Upload to server for persistent storage.
108
114
  const manifest = {
109
115
  id: meta.id,
110
116
  type: meta.type,
@@ -114,13 +120,13 @@
114
120
  sourceRegistry: meta.sourceRegistry,
115
121
  installedAt: new Date().toISOString(),
116
122
  };
117
- const serverResult = await serverInstallPackage(manifest, bundle);
123
+ const serverResult = await serverInstallPackage(manifest, bundle, serverBundle);
118
124
  if (!serverResult.ok) {
119
125
  installError = serverResult.error ?? 'Server install failed';
120
126
  return;
121
127
  }
122
128
 
123
- // 3. Also install locally for immediate hot-load.
129
+ // 4. Also install locally for immediate hot-load.
124
130
  const result = await installPackage(bundle, meta);
125
131
  if (!result.success) {
126
132
  console.warn(`[sh3-store] Server install ok but local hot-load failed: ${result.error}`);
@@ -6,11 +6,12 @@
6
6
  * Framework-shipped: registered in host.ts during bootstrap.
7
7
  * Admin-gated via manifest flag (ADR-011).
8
8
  */
9
+ import { VERSION } from '../../version';
9
10
  export const storeApp = {
10
11
  manifest: {
11
12
  id: 'sh3-store-app',
12
13
  label: 'Package Store',
13
- version: '0.2.1',
14
+ version: VERSION,
14
15
  requiredShards: ['sh3-store'],
15
16
  layoutVersion: 1,
16
17
  admin: true,
@@ -14,10 +14,12 @@
14
14
  import { mount, unmount } from 'svelte';
15
15
  import StoreView from './StoreView.svelte';
16
16
  import InstalledView from './InstalledView.svelte';
17
- import { fetchRegistries, fetchBundle, buildPackageMeta } from '../../registry/client';
17
+ import { fetchRegistries, fetchBundle, fetchServerBundle, buildPackageMeta } from '../../registry/client';
18
18
  import { installPackage, listInstalledPackages } from '../../registry/installer';
19
19
  import { loadBundle, savePackage } from '../../registry/storage';
20
20
  import { serverInstallPackage, fetchServerPackages } from '../../env/client';
21
+ import { VERSION } from '../../version';
22
+ import { installVerb, uninstallVerb, appinfoVerb } from './verbs';
21
23
  /**
22
24
  * Compare two semver-like version strings.
23
25
  * Returns true only if `available` is strictly greater than `installed`.
@@ -48,7 +50,7 @@ export const storeShard = {
48
50
  manifest: {
49
51
  id: 'sh3-store',
50
52
  label: 'Package Store',
51
- version: '0.2.1',
53
+ version: VERSION,
52
54
  views: [
53
55
  { id: 'sh3-store:browse', label: 'Store' },
54
56
  { id: 'sh3-store:installed', label: 'Installed' },
@@ -139,8 +141,12 @@ export const storeShard = {
139
141
  const installedRecord = state.ephemeral.installed.find((p) => p.id === id);
140
142
  if (!installedRecord)
141
143
  return;
142
- // 1. Fetch new bundle.
144
+ // 1. Fetch new bundle(s).
143
145
  const bundle = await fetchBundle(catalogEntry.latest, catalogEntry.sourceRegistry);
146
+ let serverBundle;
147
+ if (catalogEntry.latest.serverBundleUrl) {
148
+ serverBundle = await fetchServerBundle(catalogEntry.latest, catalogEntry.sourceRegistry);
149
+ }
144
150
  const meta = buildPackageMeta(catalogEntry, catalogEntry.latest);
145
151
  // 2. Snapshot current state for rollback.
146
152
  const oldBundle = await loadBundle(id);
@@ -155,7 +161,7 @@ export const storeShard = {
155
161
  sourceRegistry: meta.sourceRegistry,
156
162
  installedAt: new Date().toISOString(),
157
163
  };
158
- const serverResult = await serverInstallPackage(manifest, bundle);
164
+ const serverResult = await serverInstallPackage(manifest, bundle, serverBundle);
159
165
  if (!serverResult.ok) {
160
166
  throw new Error((_a = serverResult.error) !== null && _a !== void 0 ? _a : 'Server update failed');
161
167
  }
@@ -208,6 +214,10 @@ export const storeShard = {
208
214
  };
209
215
  ctx.registerView('sh3-store:browse', browseFactory);
210
216
  ctx.registerView('sh3-store:installed', installedFactory);
217
+ // Store verbs — registered as sh3-store:install, sh3-store:uninstall, sh3-store:appinfo
218
+ ctx.registerVerb(installVerb);
219
+ ctx.registerVerb(uninstallVerb);
220
+ ctx.registerVerb(appinfoVerb);
211
221
  // refreshInstalled can run immediately (hits server, no env needed).
212
222
  refreshInstalled();
213
223
  },
@@ -0,0 +1,4 @@
1
+ import type { Verb } from '../../verbs/types';
2
+ export declare const installVerb: Verb;
3
+ export declare const uninstallVerb: Verb;
4
+ export declare const appinfoVerb: Verb;
@@ -0,0 +1,220 @@
1
+ /*
2
+ * Store verbs — shell-accessible commands for the sh3-store shard.
3
+ *
4
+ * Registered during storeShard.activate() via ctx.registerVerb().
5
+ * Auto-prefixed to sh3-store:install, sh3-store:uninstall, sh3-store:appinfo.
6
+ */
7
+ import { storeContext } from './storeShard.svelte';
8
+ import { fetchBundle, fetchServerBundle, buildPackageMeta } from '../../registry/client';
9
+ import { installPackage } from '../../registry/installer';
10
+ import { serverInstallPackage, serverUninstallPackage } from '../../env/client';
11
+ import { uninstallPackage } from '../../registry/installer';
12
+ function findInCatalog(id) {
13
+ return storeContext.state.ephemeral.catalog.find((p) => p.entry.id === id);
14
+ }
15
+ function findInstalled(id) {
16
+ return storeContext.state.ephemeral.installed.find((p) => p.id === id);
17
+ }
18
+ export const installVerb = {
19
+ name: 'install',
20
+ summary: 'Install a package by id from the catalog.',
21
+ async run(ctx, args) {
22
+ var _a;
23
+ const id = args[0];
24
+ if (!id) {
25
+ ctx.scrollback.push({
26
+ kind: 'status',
27
+ text: 'usage: sh3-store:install <package-id>',
28
+ level: 'warn',
29
+ ts: Date.now(),
30
+ });
31
+ return;
32
+ }
33
+ const existing = findInstalled(id);
34
+ if (existing) {
35
+ ctx.scrollback.push({
36
+ kind: 'status',
37
+ text: `${id} is already installed (v${existing.version})`,
38
+ level: 'warn',
39
+ ts: Date.now(),
40
+ });
41
+ return;
42
+ }
43
+ const pkg = findInCatalog(id);
44
+ if (!pkg) {
45
+ ctx.scrollback.push({
46
+ kind: 'status',
47
+ text: `package "${id}" not found in catalog — try refreshing the store`,
48
+ level: 'error',
49
+ ts: Date.now(),
50
+ });
51
+ return;
52
+ }
53
+ ctx.scrollback.push({
54
+ kind: 'status',
55
+ text: `installing ${id} v${pkg.latest.version}...`,
56
+ level: 'info',
57
+ ts: Date.now(),
58
+ });
59
+ try {
60
+ const bundle = await fetchBundle(pkg.latest, pkg.sourceRegistry);
61
+ const meta = buildPackageMeta(pkg, pkg.latest);
62
+ let serverBundle;
63
+ if (pkg.latest.serverBundleUrl) {
64
+ serverBundle = await fetchServerBundle(pkg.latest, pkg.sourceRegistry);
65
+ }
66
+ const manifest = {
67
+ id: meta.id,
68
+ type: meta.type,
69
+ label: pkg.entry.label,
70
+ version: meta.version,
71
+ contractVersion: meta.contractVersion,
72
+ sourceRegistry: meta.sourceRegistry,
73
+ installedAt: new Date().toISOString(),
74
+ };
75
+ const serverResult = await serverInstallPackage(manifest, bundle, serverBundle);
76
+ if (!serverResult.ok) {
77
+ ctx.scrollback.push({
78
+ kind: 'status',
79
+ text: `install failed: ${(_a = serverResult.error) !== null && _a !== void 0 ? _a : 'server error'}`,
80
+ level: 'error',
81
+ ts: Date.now(),
82
+ });
83
+ return;
84
+ }
85
+ const result = await installPackage(bundle, meta);
86
+ if (!result.success) {
87
+ ctx.scrollback.push({
88
+ kind: 'status',
89
+ text: `server ok but local hot-load failed: ${result.error}`,
90
+ level: 'warn',
91
+ ts: Date.now(),
92
+ });
93
+ }
94
+ await storeContext.refreshInstalled();
95
+ ctx.scrollback.push({
96
+ kind: 'status',
97
+ text: `installed ${id} v${pkg.latest.version}`,
98
+ level: 'info',
99
+ ts: Date.now(),
100
+ });
101
+ }
102
+ catch (err) {
103
+ ctx.scrollback.push({
104
+ kind: 'status',
105
+ text: `install failed: ${err instanceof Error ? err.message : String(err)}`,
106
+ level: 'error',
107
+ ts: Date.now(),
108
+ });
109
+ }
110
+ },
111
+ };
112
+ export const uninstallVerb = {
113
+ name: 'uninstall',
114
+ summary: 'Uninstall an installed package by id.',
115
+ async run(ctx, args) {
116
+ const id = args[0];
117
+ if (!id) {
118
+ ctx.scrollback.push({
119
+ kind: 'status',
120
+ text: 'usage: sh3-store:uninstall <package-id>',
121
+ level: 'warn',
122
+ ts: Date.now(),
123
+ });
124
+ return;
125
+ }
126
+ const existing = findInstalled(id);
127
+ if (!existing) {
128
+ ctx.scrollback.push({
129
+ kind: 'status',
130
+ text: `package "${id}" is not installed`,
131
+ level: 'error',
132
+ ts: Date.now(),
133
+ });
134
+ return;
135
+ }
136
+ ctx.scrollback.push({
137
+ kind: 'status',
138
+ text: `uninstalling ${id}...`,
139
+ level: 'info',
140
+ ts: Date.now(),
141
+ });
142
+ try {
143
+ await serverUninstallPackage(id);
144
+ await uninstallPackage(id);
145
+ await storeContext.refreshInstalled();
146
+ ctx.scrollback.push({
147
+ kind: 'status',
148
+ text: `uninstalled ${id}`,
149
+ level: 'info',
150
+ ts: Date.now(),
151
+ });
152
+ }
153
+ catch (err) {
154
+ ctx.scrollback.push({
155
+ kind: 'status',
156
+ text: `uninstall failed: ${err instanceof Error ? err.message : String(err)}`,
157
+ level: 'error',
158
+ ts: Date.now(),
159
+ });
160
+ }
161
+ },
162
+ };
163
+ export const appinfoVerb = {
164
+ name: 'appinfo',
165
+ summary: 'Show info about a package (installed status, version, catalog details).',
166
+ async run(ctx, args) {
167
+ const id = args[0];
168
+ if (!id) {
169
+ ctx.scrollback.push({
170
+ kind: 'status',
171
+ text: 'usage: sh3-store:appinfo <package-id>',
172
+ level: 'warn',
173
+ ts: Date.now(),
174
+ });
175
+ return;
176
+ }
177
+ const installed = findInstalled(id);
178
+ const catalogEntry = findInCatalog(id);
179
+ if (!installed && !catalogEntry) {
180
+ ctx.scrollback.push({
181
+ kind: 'status',
182
+ text: `package "${id}" not found in catalog or installed list`,
183
+ level: 'error',
184
+ ts: Date.now(),
185
+ });
186
+ return;
187
+ }
188
+ const lines = [];
189
+ lines.push(`--- ${id} ---`);
190
+ if (installed) {
191
+ lines.push(` installed: yes`);
192
+ lines.push(` version: ${installed.version}`);
193
+ lines.push(` type: ${installed.type}`);
194
+ lines.push(` contract: v${installed.contractVersion}`);
195
+ lines.push(` source: ${installed.sourceRegistry}`);
196
+ if (installed.installedAt) {
197
+ lines.push(` installed: ${installed.installedAt}`);
198
+ }
199
+ }
200
+ else {
201
+ lines.push(` installed: no`);
202
+ }
203
+ if (catalogEntry) {
204
+ lines.push(` catalog: yes`);
205
+ lines.push(` latest: ${catalogEntry.latest.version}`);
206
+ lines.push(` label: ${catalogEntry.entry.label}`);
207
+ lines.push(` author: ${catalogEntry.entry.author.name}`);
208
+ lines.push(` desc: ${catalogEntry.entry.description}`);
209
+ }
210
+ else {
211
+ lines.push(` catalog: no (not in any configured registry)`);
212
+ }
213
+ ctx.scrollback.push({
214
+ kind: 'text',
215
+ stream: 'stdout',
216
+ chunks: [lines.join('\n') + '\n'],
217
+ ts: Date.now(),
218
+ });
219
+ },
220
+ };
@@ -4,10 +4,11 @@
4
4
  * Framework-shipped: registered in host.ts during bootstrap.
5
5
  * Requires the `shell` shard which provides the `shell:terminal` view.
6
6
  */
7
+ import { VERSION } from '../../version';
7
8
  export const manifest = {
8
9
  id: 'terminal',
9
10
  label: 'Terminal',
10
- version: '0.1.0',
11
+ version: VERSION,
11
12
  requiredShards: ['shell'],
12
13
  layoutVersion: 1,
13
14
  };
@@ -1,17 +1,19 @@
1
- import type { LayoutNode } from '../layout/types';
1
+ import type { LayoutNode, LayoutTree, LayoutPreset } from '../layout/types';
2
2
  import type { ZoneSchema, ZoneManager } from '../state/types';
3
3
  import type { StateZones } from '../state/zones.svelte';
4
4
  /**
5
- * Static description of an app. Declared by every app module and read by
6
- * the shell launcher to populate the home screen list without running any
7
- * activation code.
5
+ * Static description of an app as observed by the framework at runtime.
6
+ * `version` is always present here: externally installed apps have it
7
+ * stamped by the installer from the registry entry, in-tree apps import
8
+ * it from `VERSION` (auto-generated from `sh3-core`'s `package.json`).
9
+ * See ADR-013.
8
10
  */
9
11
  export interface AppManifest {
10
12
  /** Unique app identifier. Must match the app's registration key. */
11
13
  id: string;
12
14
  /** Human-readable display name shown in the launcher. */
13
15
  label: string;
14
- /** Semver string for the app package. */
16
+ /** Semver version string. Always present at load time — see `SourceAppManifest` for what authors write. */
15
17
  version: string;
16
18
  /**
17
19
  * Shard ids this app requires. All required shards are activated at
@@ -71,8 +73,12 @@ export interface AppContext {
71
73
  export interface App {
72
74
  /** Static manifest describing the app and its requirements. */
73
75
  manifest: AppManifest;
74
- /** Starting layout tree. Used on first launch or when the persisted layout version mismatches. */
75
- initialLayout: LayoutNode;
76
+ /**
77
+ * App's default layout — accepts three shapes: a bare LayoutNode
78
+ * (legacy), a LayoutTree (single-preset with floats), or an array of
79
+ * LayoutPresets (multiple named layouts). Normalized at load time.
80
+ */
81
+ initialLayout: LayoutNode | LayoutTree | LayoutPreset[];
76
82
  /** Optional hook called after all required shards are active and the layout is attached. */
77
83
  activate?(ctx: AppContext): void | Promise<void>;
78
84
  /** Optional hook called before the app's shards are deactivated and the layout is detached. */
@@ -91,3 +97,18 @@ export interface App {
91
97
  */
92
98
  resume?(ctx: AppContext): void | Promise<void>;
93
99
  }
100
+ /**
101
+ * Source-declared shape of an app manifest — what external package authors
102
+ * write. `version` is omitted because it would duplicate `package.json.version`;
103
+ * the framework injects it at load time. See ADR-013.
104
+ */
105
+ export type SourceAppManifest = Omit<AppManifest, 'version'>;
106
+ /**
107
+ * Source-level shape of an app as written by external package authors.
108
+ * Carries a `SourceAppManifest` (no `version`). The framework stamps
109
+ * `version` from the registry entry's `PackageVersion.version` at install
110
+ * time, after which the object is observed as a full `App`. See ADR-013.
111
+ */
112
+ export interface SourceApp extends Omit<App, 'manifest'> {
113
+ manifest: SourceAppManifest;
114
+ }
package/dist/build.d.ts CHANGED
@@ -43,7 +43,10 @@ export interface Sh3ArtifactOptions {
43
43
  * After Vite's lib build completes, this plugin:
44
44
  * 1. Finds the entry chunk and renames it to client.js
45
45
  * 2. Optionally copies a pre-built server bundle as server.js
46
- * 3. Extracts manifest fields (id, label, version, type) from the source
47
- * 4. Writes manifest.json alongside the bundles
46
+ * 3. Extracts manifest fields (id, label, type) from the source
47
+ * 4. Reads `version` from `package.json.version` the authoritative
48
+ * source per ADR-013. A literal `version:` in a source manifest is
49
+ * ignored; only `package.json.version` matters for the artifact.
50
+ * 5. Writes manifest.json alongside the bundles
48
51
  */
49
52
  export declare function sh3Artifact(options?: Sh3ArtifactOptions): Plugin;
package/dist/build.js CHANGED
@@ -89,8 +89,11 @@ export function sh3CssInline() {
89
89
  * After Vite's lib build completes, this plugin:
90
90
  * 1. Finds the entry chunk and renames it to client.js
91
91
  * 2. Optionally copies a pre-built server bundle as server.js
92
- * 3. Extracts manifest fields (id, label, version, type) from the source
93
- * 4. Writes manifest.json alongside the bundles
92
+ * 3. Extracts manifest fields (id, label, type) from the source
93
+ * 4. Reads `version` from `package.json.version` the authoritative
94
+ * source per ADR-013. A literal `version:` in a source manifest is
95
+ * ignored; only `package.json.version` matters for the artifact.
96
+ * 5. Writes manifest.json alongside the bundles
94
97
  */
95
98
  export function sh3Artifact(options = {}) {
96
99
  let outDir = '';
@@ -145,10 +148,11 @@ export function sh3Artifact(options = {}) {
145
148
  }
146
149
  const type = hasShard && hasApp ? 'combo' : hasApp ? 'app' : 'shard';
147
150
  /**
148
- * Extract id, label, and version from the manifest object block that
149
- * contains `anchor`. Walks backwards from the anchor to find the
150
- * opening `{`, then forward to find the matching `}`, and extracts
151
- * fields from within that slice.
151
+ * Extract id and label from the manifest object block that contains
152
+ * `anchor`. Walks backwards from the anchor to find the opening `{`,
153
+ * then forward to find the matching `}`, and extracts fields from
154
+ * within that slice. `version` is NOT extracted from the source —
155
+ * per ADR-013 the authoritative value is `package.json.version`.
152
156
  */
153
157
  function extractFromBlock(anchor) {
154
158
  const anchorMatch = anchor.exec(source);
@@ -190,29 +194,36 @@ export function sh3Artifact(options = {}) {
190
194
  return {
191
195
  id: get(/\bid\s*:\s*["']([^"']+)["']/),
192
196
  label: get(/\blabel\s*:\s*["']([^"']+)["']/),
193
- version: get(/\bversion\s*:\s*["']([^"']+)["']/),
194
197
  };
195
198
  }
196
199
  // App first, then Shard.
197
200
  const extracted = (_a = extractFromBlock(/\brequiredShards\s*:\s*\[/)) !== null && _a !== void 0 ? _a : extractFromBlock(/\bviews\s*:\s*\[/);
198
- const { id, label, version } = extracted;
201
+ const { id, label } = extracted;
199
202
  // --- Optional server bundle ---
200
203
  let hasServer = false;
201
204
  if (options.serverEntry && existsSync(options.serverEntry)) {
202
205
  copyFileSync(options.serverEntry, join(outDir, 'server.js'));
203
206
  hasServer = true;
204
207
  }
205
- // --- Read fallback metadata from package.json ---
208
+ // --- Read metadata from package.json ---
209
+ // `version` is authoritative per ADR-013; `description` and `author`
210
+ // are fallbacks when the user doesn't pass overrides.
211
+ let pkgVersion;
206
212
  let pkgDescription;
207
213
  let pkgAuthor;
208
214
  try {
209
215
  const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
216
+ pkgVersion = typeof pkg.version === 'string' ? pkg.version : undefined;
210
217
  pkgDescription = typeof pkg.description === 'string' ? pkg.description : undefined;
211
218
  pkgAuthor = typeof pkg.author === 'string'
212
219
  ? pkg.author
213
220
  : typeof ((_b = pkg.author) === null || _b === void 0 ? void 0 : _b.name) === 'string' ? pkg.author.name : undefined;
214
221
  }
215
222
  catch ( /* no package.json or unreadable */_g) { /* no package.json or unreadable */ }
223
+ if (!pkgVersion) {
224
+ throw new Error('[sh3-artifact] Missing "version" in package.json. Per ADR-013 the package '
225
+ + 'version is read from package.json, not from the source manifest.');
226
+ }
216
227
  // --- Write manifest.json ---
217
228
  const overrides = (_c = options.manifest) !== null && _c !== void 0 ? _c : {};
218
229
  const finalDescription = (_d = overrides.description) !== null && _d !== void 0 ? _d : pkgDescription;
@@ -223,7 +234,7 @@ export function sh3Artifact(options = {}) {
223
234
  if (!finalAuthor) {
224
235
  throw new Error('[sh3-artifact] Missing "author". Add it to package.json or pass it via sh3Artifact({ manifest: { author } }).');
225
236
  }
226
- const manifest = Object.assign(Object.assign(Object.assign({ id: id || 'unknown', type, label: label || id || 'unknown', version: version || '0.0.0', contractVersion: 1, client: 'client.js' }, (hasServer ? { server: 'server.js' } : {})), { description: finalDescription, author: finalAuthor }), overrides);
237
+ const manifest = Object.assign(Object.assign(Object.assign({ id: id || 'unknown', type, label: label || id || 'unknown', version: pkgVersion, contractVersion: 1, client: 'client.js' }, (hasServer ? { server: 'server.js' } : {})), { description: finalDescription, author: finalAuthor }), overrides);
227
238
  writeFileSync(join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
228
239
  // --- Log summary ---
229
240
  const files = ['manifest.json', 'client.js'];