sh3-core 0.6.0 → 0.7.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 (69) hide show
  1. package/dist/Shell.svelte +20 -14
  2. package/dist/api.d.ts +5 -3
  3. package/dist/app/admin/adminApp.js +2 -1
  4. package/dist/app/admin/adminShard.svelte.js +2 -1
  5. package/dist/app/store/StoreView.svelte +11 -5
  6. package/dist/app/store/storeApp.js +2 -1
  7. package/dist/app/store/storeShard.svelte.js +9 -4
  8. package/dist/apps/terminal/manifest.js +2 -1
  9. package/dist/apps/types.d.ts +28 -7
  10. package/dist/build.d.ts +5 -2
  11. package/dist/build.js +21 -10
  12. package/dist/env/client.d.ts +10 -2
  13. package/dist/env/client.js +13 -2
  14. package/dist/layout/LayoutRenderer.svelte +21 -9
  15. package/dist/layout/LayoutRenderer.svelte.d.ts +2 -0
  16. package/dist/layout/SlotDropZone.svelte +4 -1
  17. package/dist/layout/SlotDropZone.svelte.d.ts +2 -0
  18. package/dist/layout/drag.svelte.d.ts +5 -2
  19. package/dist/layout/drag.svelte.js +43 -11
  20. package/dist/layout/floats.d.ts +35 -0
  21. package/dist/layout/floats.js +73 -0
  22. package/dist/layout/floats.test.d.ts +1 -0
  23. package/dist/layout/floats.test.js +114 -0
  24. package/dist/layout/inspection.d.ts +2 -2
  25. package/dist/layout/inspection.js +6 -6
  26. package/dist/layout/ops.d.ts +14 -1
  27. package/dist/layout/ops.js +17 -0
  28. package/dist/layout/ops.test.d.ts +1 -0
  29. package/dist/layout/ops.test.js +36 -0
  30. package/dist/layout/presets.d.ts +2 -0
  31. package/dist/layout/presets.js +49 -0
  32. package/dist/layout/presets.test.d.ts +1 -0
  33. package/dist/layout/presets.test.js +71 -0
  34. package/dist/layout/store.svelte.d.ts +17 -13
  35. package/dist/layout/store.svelte.js +98 -36
  36. package/dist/layout/tree-walk.d.ts +12 -1
  37. package/dist/layout/tree-walk.js +13 -0
  38. package/dist/layout/tree-walk.test.d.ts +1 -0
  39. package/dist/layout/tree-walk.test.js +41 -0
  40. package/dist/layout/types.d.ts +96 -6
  41. package/dist/layout/types.js +1 -1
  42. package/dist/overlays/FloatFrame.svelte +141 -0
  43. package/dist/overlays/FloatFrame.svelte.d.ts +7 -0
  44. package/dist/overlays/FloatLayer.svelte +28 -0
  45. package/dist/overlays/FloatLayer.svelte.d.ts +3 -0
  46. package/dist/overlays/float.d.ts +29 -0
  47. package/dist/overlays/float.js +119 -0
  48. package/dist/overlays/float.test.d.ts +1 -0
  49. package/dist/overlays/float.test.js +37 -0
  50. package/dist/overlays/presets.d.ts +21 -0
  51. package/dist/overlays/presets.js +63 -0
  52. package/dist/overlays/presets.test.d.ts +1 -0
  53. package/dist/overlays/presets.test.js +40 -0
  54. package/dist/registry/client.d.ts +14 -0
  55. package/dist/registry/client.js +37 -0
  56. package/dist/registry/client.test.d.ts +1 -0
  57. package/dist/registry/client.test.js +54 -0
  58. package/dist/registry/installer.js +18 -5
  59. package/dist/registry/schema.js +5 -0
  60. package/dist/registry/types.d.ts +9 -0
  61. package/dist/shards/types.d.ts +27 -4
  62. package/dist/shell-shard/Terminal.svelte +14 -8
  63. package/dist/shell-shard/manifest.js +2 -1
  64. package/dist/shell-shard/shellShard.svelte.js +2 -1
  65. package/dist/shellRuntime.svelte.d.ts +6 -0
  66. package/dist/shellRuntime.svelte.js +4 -0
  67. package/dist/version.d.ts +1 -1
  68. package/dist/version.js +1 -1
  69. 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';
@@ -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,11 @@
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';
21
22
  /**
22
23
  * Compare two semver-like version strings.
23
24
  * Returns true only if `available` is strictly greater than `installed`.
@@ -48,7 +49,7 @@ export const storeShard = {
48
49
  manifest: {
49
50
  id: 'sh3-store',
50
51
  label: 'Package Store',
51
- version: '0.2.1',
52
+ version: VERSION,
52
53
  views: [
53
54
  { id: 'sh3-store:browse', label: 'Store' },
54
55
  { id: 'sh3-store:installed', label: 'Installed' },
@@ -139,8 +140,12 @@ export const storeShard = {
139
140
  const installedRecord = state.ephemeral.installed.find((p) => p.id === id);
140
141
  if (!installedRecord)
141
142
  return;
142
- // 1. Fetch new bundle.
143
+ // 1. Fetch new bundle(s).
143
144
  const bundle = await fetchBundle(catalogEntry.latest, catalogEntry.sourceRegistry);
145
+ let serverBundle;
146
+ if (catalogEntry.latest.serverBundleUrl) {
147
+ serverBundle = await fetchServerBundle(catalogEntry.latest, catalogEntry.sourceRegistry);
148
+ }
144
149
  const meta = buildPackageMeta(catalogEntry, catalogEntry.latest);
145
150
  // 2. Snapshot current state for rollback.
146
151
  const oldBundle = await loadBundle(id);
@@ -155,7 +160,7 @@ export const storeShard = {
155
160
  sourceRegistry: meta.sourceRegistry,
156
161
  installedAt: new Date().toISOString(),
157
162
  };
158
- const serverResult = await serverInstallPackage(manifest, bundle);
163
+ const serverResult = await serverInstallPackage(manifest, bundle, serverBundle);
159
164
  if (!serverResult.ok) {
160
165
  throw new Error((_a = serverResult.error) !== null && _a !== void 0 ? _a : 'Server update failed');
161
166
  }
@@ -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'];
@@ -18,9 +18,17 @@ export declare function fetchEnvState(shardId: string): Promise<Record<string, u
18
18
  export declare function putEnvState(shardId: string, state: Record<string, unknown>): Promise<void>;
19
19
  /**
20
20
  * Install a package on the server via multipart upload.
21
- * The client has already fetched and integrity-verified the bundle.
21
+ * The client has already fetched and integrity-verified the client bundle
22
+ * (and the server bundle, if present and serverIntegrity was declared).
23
+ *
24
+ * @param manifest - Package manifest metadata to persist server-side.
25
+ * @param clientBundle - Verified client ESM bundle bytes.
26
+ * @param serverBundle - Optional verified server ESM bundle bytes. When
27
+ * provided, the server writes it to `server.js` and hot-mounts the
28
+ * shard's routes. If the mount fails, the entire install is rolled
29
+ * back server-side.
22
30
  */
23
- export declare function serverInstallPackage(manifest: Record<string, unknown>, clientBundle: ArrayBuffer): Promise<{
31
+ export declare function serverInstallPackage(manifest: Record<string, unknown>, clientBundle: ArrayBuffer, serverBundle?: ArrayBuffer): Promise<{
24
32
  ok: boolean;
25
33
  id: string;
26
34
  error?: string;
@@ -50,9 +50,17 @@ export async function putEnvState(shardId, state) {
50
50
  }
51
51
  /**
52
52
  * Install a package on the server via multipart upload.
53
- * The client has already fetched and integrity-verified the bundle.
53
+ * The client has already fetched and integrity-verified the client bundle
54
+ * (and the server bundle, if present and serverIntegrity was declared).
55
+ *
56
+ * @param manifest - Package manifest metadata to persist server-side.
57
+ * @param clientBundle - Verified client ESM bundle bytes.
58
+ * @param serverBundle - Optional verified server ESM bundle bytes. When
59
+ * provided, the server writes it to `server.js` and hot-mounts the
60
+ * shard's routes. If the mount fails, the entire install is rolled
61
+ * back server-side.
54
62
  */
55
- export async function serverInstallPackage(manifest, clientBundle) {
63
+ export async function serverInstallPackage(manifest, clientBundle, serverBundle) {
56
64
  var _a;
57
65
  if (!isAdmin())
58
66
  throw new Error('Cannot install: not elevated to admin');
@@ -60,6 +68,9 @@ export async function serverInstallPackage(manifest, clientBundle) {
60
68
  const form = new FormData();
61
69
  form.append('manifest', new Blob([JSON.stringify(manifest)], { type: 'application/json' }), 'manifest.json');
62
70
  form.append('client', new Blob([clientBundle], { type: 'application/javascript' }), 'client.js');
71
+ if (serverBundle !== undefined) {
72
+ form.append('server', new Blob([serverBundle], { type: 'application/javascript' }), 'server.js');
73
+ }
63
74
  const headers = {};
64
75
  if (auth)
65
76
  headers['Authorization'] = auth;
@@ -26,7 +26,7 @@
26
26
  * path via `[...path, i]`.
27
27
  */
28
28
 
29
- import type { TabsNode, LayoutNode } from './types';
29
+ import type { TabsNode, LayoutNode, TreeRootRef } from './types';
30
30
  import ResizableSplitter from '../primitives/ResizableSplitter.svelte';
31
31
  import TabbedPanel, { type TabDragController } from '../primitives/TabbedPanel.svelte';
32
32
  import SlotContainer from './SlotContainer.svelte';
@@ -44,7 +44,10 @@
44
44
  suppressNextClick,
45
45
  } from './drag.svelte';
46
46
 
47
- let { path = [] }: { path?: number[] } = $props();
47
+ let {
48
+ path = [],
49
+ rootRef = { kind: 'docked' } as TreeRootRef,
50
+ }: { path?: number[]; rootRef?: TreeRootRef } = $props();
48
51
 
49
52
  /**
50
53
  * Resolve the current node by walking `layoutStore.root` along the
@@ -53,7 +56,16 @@
53
56
  * cleanup pass can collapse nodes out from under a recursive
54
57
  * renderer), we render null.
55
58
  */
56
- const node = $derived(nodeAtPath(layoutStore.root, path));
59
+ const node = $derived.by(() => {
60
+ let rootNode: LayoutNode | null;
61
+ if (rootRef.kind === 'docked') {
62
+ rootNode = layoutStore.root;
63
+ } else {
64
+ const entry = layoutStore.tree.floats.find((f) => f.id === rootRef.floatId);
65
+ rootNode = entry?.content ?? null;
66
+ }
67
+ return rootNode ? nodeAtPath(rootNode, path) : null;
68
+ });
57
69
 
58
70
  /**
59
71
  * Build a TabDragController bound to the current tabs node.
@@ -68,7 +80,7 @@
68
80
  onPointerDown(index, event, element) {
69
81
  const entry = tabsNode.tabs[index];
70
82
  if (!entry) return;
71
- beginTabDrag(entry.slotId, entry, event, element);
83
+ beginTabDrag(entry.slotId, entry, rootRef, event, element);
72
84
  },
73
85
  onStripHover(stripRect, pointerX, pointerY, tabRects) {
74
86
  if (pointerY < stripRect.top || pointerY > stripRect.bottom) {
@@ -92,7 +104,7 @@
92
104
  const srcIdx = tabsNode.tabs.findIndex((t) => t.slotId === source.slotId);
93
105
  if (srcIdx >= 0 && srcIdx < insertIndex) insertIndex -= 1;
94
106
  }
95
- setDropTarget({ kind: 'strip', tabsNode, insertIndex });
107
+ setDropTarget({ kind: 'strip', root: rootRef, tabsNode, insertIndex });
96
108
  return insertIndex;
97
109
  },
98
110
  onStripLeave() {
@@ -141,7 +153,7 @@
141
153
  */
142
154
  function onEmptyTabsDrop(_e: PointerEvent, tabsNode: import('./types').TabsNode) {
143
155
  if (dragState.phase !== 'dragging') return;
144
- setDropTarget({ kind: 'strip', tabsNode, insertIndex: 0 });
156
+ setDropTarget({ kind: 'strip', root: rootRef, tabsNode, insertIndex: 0 });
145
157
  }
146
158
 
147
159
  function onEmptyTabsLeave() {
@@ -166,7 +178,7 @@
166
178
  }}
167
179
  />
168
180
  {#snippet splitPane(i: number)}
169
- <Self path={[...path, i]} />
181
+ <Self {rootRef} path={[...path, i]} />
170
182
  {/snippet}
171
183
  {:else if node.type === 'tabs'}
172
184
  {@const tabs = asTabs(node)}
@@ -189,7 +201,7 @@
189
201
  {#if entry}
190
202
  <div class="tab-slot-wrapper">
191
203
  <SlotContainer node={{ type: 'slot', slotId: entry.slotId, viewId: entry.viewId }} label={entry.label} />
192
- <SlotDropZone path={path} />
204
+ <SlotDropZone {rootRef} path={path} />
193
205
  </div>
194
206
  {/if}
195
207
  {/snippet}
@@ -213,7 +225,7 @@
213
225
  {@const slot = asSlot(node)!}
214
226
  <div class="leaf-slot-wrapper">
215
227
  <SlotContainer node={slot} />
216
- <SlotDropZone path={path} />
228
+ <SlotDropZone {rootRef} path={path} />
217
229
  </div>
218
230
  {/if}
219
231
  {/if}
@@ -1,5 +1,7 @@
1
+ import type { TreeRootRef } from './types';
1
2
  type $$ComponentProps = {
2
3
  path?: number[];
4
+ rootRef?: TreeRootRef;
3
5
  };
4
6
  declare const LayoutRenderer: import("svelte").Component<$$ComponentProps, {}, "">;
5
7
  type LayoutRenderer = ReturnType<typeof LayoutRenderer>;
@@ -21,10 +21,13 @@
21
21
 
22
22
  import { dragState, setDropTarget, clearDropTarget, type DropTarget } from './drag.svelte';
23
23
  import type { LayoutPath, SplitSide } from './ops';
24
+ import type { TreeRootRef } from './types';
24
25
 
25
26
  let {
27
+ rootRef,
26
28
  path,
27
29
  }: {
30
+ rootRef: TreeRootRef;
28
31
  /** Path of the node this zone covers, used when reporting the drop. */
29
32
  path: LayoutPath;
30
33
  } = $props();
@@ -72,7 +75,7 @@
72
75
  const side = quadrantFor(e.clientX, e.clientY, rect);
73
76
  if (side !== hoveredSide) {
74
77
  hoveredSide = side;
75
- const target: DropTarget = { kind: 'split', path: [...path], side };
78
+ const target: DropTarget = { kind: 'split', root: rootRef, path: [...path], side };
76
79
  setDropTarget(target);
77
80
  }
78
81
  }
@@ -1,5 +1,7 @@
1
1
  import type { LayoutPath } from './ops';
2
+ import type { TreeRootRef } from './types';
2
3
  type $$ComponentProps = {
4
+ rootRef: TreeRootRef;
3
5
  /** Path of the node this zone covers, used when reporting the drop. */
4
6
  path: LayoutPath;
5
7
  };
@@ -1,18 +1,21 @@
1
- import type { TabEntry, TabsNode } from './types';
1
+ import type { TabEntry, TabsNode, TreeRootRef } from './types';
2
2
  import { type LayoutPath, type SplitSide } from './ops';
3
3
  export type DropTarget = {
4
4
  kind: 'strip';
5
+ root: TreeRootRef;
5
6
  tabsNode: TabsNode;
6
7
  /** Insertion index within tabsNode.tabs (0..length). */
7
8
  insertIndex: number;
8
9
  } | {
9
10
  kind: 'split';
11
+ root: TreeRootRef;
10
12
  path: LayoutPath;
11
13
  side: SplitSide;
12
14
  };
13
15
  interface DragSource {
14
16
  slotId: string;
15
17
  entry: TabEntry;
18
+ sourceRoot: TreeRootRef;
16
19
  /** The tab's viewport rect at drag start — used to offset the ghost. */
17
20
  startRect: DOMRect;
18
21
  /** Pointer offset inside the tab at drag start. */
@@ -33,7 +36,7 @@ export declare function suppressNextClick(): boolean;
33
36
  * This does not yet enter the dragging phase — movement past the
34
37
  * threshold is required.
35
38
  */
36
- export declare function beginTabDrag(slotId: string, entry: TabEntry, event: PointerEvent, tabElement: HTMLElement): void;
39
+ export declare function beginTabDrag(slotId: string, entry: TabEntry, sourceRoot: TreeRootRef, event: PointerEvent, tabElement: HTMLElement): void;
37
40
  /**
38
41
  * Called by drop zone components when the pointer is over them. The
39
42
  * last call wins, so innermost / most-specific zones should call this