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.
- package/dist/Shell.svelte +20 -14
- package/dist/api.d.ts +5 -3
- package/dist/app/admin/adminApp.js +2 -1
- package/dist/app/admin/adminShard.svelte.js +2 -1
- package/dist/app/store/StoreView.svelte +11 -5
- package/dist/app/store/storeApp.js +2 -1
- package/dist/app/store/storeShard.svelte.js +9 -4
- package/dist/apps/terminal/manifest.js +2 -1
- package/dist/apps/types.d.ts +28 -7
- package/dist/build.d.ts +5 -2
- package/dist/build.js +21 -10
- package/dist/env/client.d.ts +10 -2
- package/dist/env/client.js +13 -2
- package/dist/layout/LayoutRenderer.svelte +21 -9
- package/dist/layout/LayoutRenderer.svelte.d.ts +2 -0
- package/dist/layout/SlotDropZone.svelte +4 -1
- package/dist/layout/SlotDropZone.svelte.d.ts +2 -0
- package/dist/layout/drag.svelte.d.ts +5 -2
- package/dist/layout/drag.svelte.js +43 -11
- package/dist/layout/floats.d.ts +35 -0
- package/dist/layout/floats.js +73 -0
- package/dist/layout/floats.test.d.ts +1 -0
- package/dist/layout/floats.test.js +114 -0
- package/dist/layout/inspection.d.ts +2 -2
- package/dist/layout/inspection.js +6 -6
- package/dist/layout/ops.d.ts +14 -1
- package/dist/layout/ops.js +17 -0
- package/dist/layout/ops.test.d.ts +1 -0
- package/dist/layout/ops.test.js +36 -0
- package/dist/layout/presets.d.ts +2 -0
- package/dist/layout/presets.js +49 -0
- package/dist/layout/presets.test.d.ts +1 -0
- package/dist/layout/presets.test.js +71 -0
- package/dist/layout/store.svelte.d.ts +17 -13
- package/dist/layout/store.svelte.js +98 -36
- package/dist/layout/tree-walk.d.ts +12 -1
- package/dist/layout/tree-walk.js +13 -0
- package/dist/layout/tree-walk.test.d.ts +1 -0
- package/dist/layout/tree-walk.test.js +41 -0
- package/dist/layout/types.d.ts +96 -6
- package/dist/layout/types.js +1 -1
- package/dist/overlays/FloatFrame.svelte +141 -0
- package/dist/overlays/FloatFrame.svelte.d.ts +7 -0
- package/dist/overlays/FloatLayer.svelte +28 -0
- package/dist/overlays/FloatLayer.svelte.d.ts +3 -0
- package/dist/overlays/float.d.ts +29 -0
- package/dist/overlays/float.js +119 -0
- package/dist/overlays/float.test.d.ts +1 -0
- package/dist/overlays/float.test.js +37 -0
- package/dist/overlays/presets.d.ts +21 -0
- package/dist/overlays/presets.js +63 -0
- package/dist/overlays/presets.test.d.ts +1 -0
- package/dist/overlays/presets.test.js +40 -0
- package/dist/registry/client.d.ts +14 -0
- package/dist/registry/client.js +37 -0
- package/dist/registry/client.test.d.ts +1 -0
- package/dist/registry/client.test.js +54 -0
- package/dist/registry/installer.js +18 -5
- package/dist/registry/schema.js +5 -0
- package/dist/registry/types.d.ts +9 -0
- package/dist/shards/types.d.ts +27 -4
- package/dist/shell-shard/Terminal.svelte +14 -8
- package/dist/shell-shard/manifest.js +2 -1
- package/dist/shell-shard/shellShard.svelte.js +2 -1
- package/dist/shellRuntime.svelte.d.ts +6 -0
- package/dist/shellRuntime.svelte.js +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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)
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
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
|
-
|
|
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 === '
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
//
|
|
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:
|
|
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:
|
|
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:
|
|
11
|
+
version: VERSION,
|
|
11
12
|
requiredShards: ['shell'],
|
|
12
13
|
layoutVersion: 1,
|
|
13
14
|
};
|
package/dist/apps/types.d.ts
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
75
|
-
|
|
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,
|
|
47
|
-
* 4.
|
|
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,
|
|
93
|
-
* 4.
|
|
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
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
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
|
|
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
|
|
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:
|
|
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'];
|
package/dist/env/client.d.ts
CHANGED
|
@@ -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;
|
package/dist/env/client.js
CHANGED
|
@@ -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 {
|
|
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(
|
|
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}
|
|
@@ -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,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
|