sh3-core 0.1.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 +185 -0
- package/dist/Shell.svelte.d.ts +4 -0
- package/dist/api.d.ts +22 -0
- package/dist/api.js +45 -0
- package/dist/apps/lifecycle.d.ts +37 -0
- package/dist/apps/lifecycle.js +153 -0
- package/dist/apps/registry.svelte.d.ts +37 -0
- package/dist/apps/registry.svelte.js +60 -0
- package/dist/apps/types.d.ts +61 -0
- package/dist/apps/types.js +10 -0
- package/dist/assets/icons.svg +1119 -0
- package/dist/auth/auth.svelte.d.ts +44 -0
- package/dist/auth/auth.svelte.js +119 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.js +85 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/host-entry.d.ts +9 -0
- package/dist/host-entry.js +15 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +260 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +191 -0
- package/dist/layout/inspection.d.ts +52 -0
- package/dist/layout/inspection.js +157 -0
- package/dist/layout/ops.d.ts +78 -0
- package/dist/layout/ops.js +281 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +150 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +118 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +170 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +180 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +245 -0
- package/dist/registry/types.js +14 -0
- package/dist/registry-shard/RegistryView.svelte +561 -0
- package/dist/registry-shard/RegistryView.svelte.d.ts +3 -0
- package/dist/registry-shard/registryApp.d.ts +10 -0
- package/dist/registry-shard/registryApp.js +24 -0
- package/dist/registry-shard/registryShard.svelte.d.ts +45 -0
- package/dist/registry-shard/registryShard.svelte.js +125 -0
- package/dist/shards/activate.svelte.d.ts +45 -0
- package/dist/shards/activate.svelte.js +124 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +155 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/ShellHome.svelte +285 -0
- package/dist/shell-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +47 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/types.d.ts +38 -0
- package/dist/state/types.js +15 -0
- package/dist/state/zones.svelte.d.ts +52 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/store/InstalledView.svelte +201 -0
- package/dist/store/InstalledView.svelte.d.ts +3 -0
- package/dist/store/StoreView.svelte +470 -0
- package/dist/store/StoreView.svelte.d.ts +3 -0
- package/dist/store/storeApp.d.ts +11 -0
- package/dist/store/storeApp.js +26 -0
- package/dist/store/storeShard.svelte.d.ts +29 -0
- package/dist/store/storeShard.svelte.js +99 -0
- package/dist/tokens.css +79 -0
- package/package.json +50 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* <Shell> — top-level chrome component.
|
|
4
|
+
*
|
|
5
|
+
* Owns the tab bar, status bar, docked content area (layer 0), and the
|
|
6
|
+
* six overlay roots (layers 1-6). This is a stub implementation for
|
|
7
|
+
* phase 1: the content area is empty and the overlay roots are present
|
|
8
|
+
* but unmanaged. Layout tree rendering arrives in phase 2; overlay layer
|
|
9
|
+
* managers arrive in phase 5.
|
|
10
|
+
*
|
|
11
|
+
* The six overlay roots are mounted here (not lazily) so that layer
|
|
12
|
+
* managers in later phases have stable DOM targets to portal into.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import './tokens.css';
|
|
16
|
+
import LayoutRenderer from './layout/LayoutRenderer.svelte';
|
|
17
|
+
import DragPreview from './layout/DragPreview.svelte';
|
|
18
|
+
import type { OverlayLayer } from './overlays/types';
|
|
19
|
+
import { registerLayerRoot, unregisterLayerRoot } from './overlays/roots';
|
|
20
|
+
import { returnToHome, getActiveApp } from './api';
|
|
21
|
+
import iconsUrl from './assets/icons.svg';
|
|
22
|
+
|
|
23
|
+
// Layer metadata — order matches the stack in docs/design/layout.md.
|
|
24
|
+
// Index 0 here is layer 1 (floating panels); layer 0 is the content area.
|
|
25
|
+
const overlayLayers: { layer: number; name: OverlayLayer }[] = [
|
|
26
|
+
{ layer: 1, name: 'floating' },
|
|
27
|
+
{ layer: 2, name: 'drag-preview' },
|
|
28
|
+
{ layer: 3, name: 'popup' },
|
|
29
|
+
{ layer: 4, name: 'modal' },
|
|
30
|
+
{ layer: 5, name: 'toast' },
|
|
31
|
+
{ layer: 6, name: 'command' },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Populated by bind:this during render; registered with the overlay
|
|
35
|
+
// module via $effect after mount so layer managers (shell.modal,
|
|
36
|
+
// shell.popup, shell.toast) can find their target DOM roots.
|
|
37
|
+
const overlayRoots: Partial<Record<OverlayLayer, HTMLDivElement>> = $state({});
|
|
38
|
+
|
|
39
|
+
$effect(() => {
|
|
40
|
+
for (const { name } of overlayLayers) {
|
|
41
|
+
const el = overlayRoots[name];
|
|
42
|
+
if (el) registerLayerRoot(name, el);
|
|
43
|
+
}
|
|
44
|
+
return () => {
|
|
45
|
+
for (const { name } of overlayLayers) unregisterLayerRoot(name);
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<div class="shell">
|
|
51
|
+
<header class="shell-tabbar" data-shell-region="tabbar">
|
|
52
|
+
<span class="shell-tabbar-brand">SH3</span>
|
|
53
|
+
{#if getActiveApp()}
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
class="shell-tabbar-home-button"
|
|
57
|
+
onclick={() => returnToHome()}
|
|
58
|
+
title="Home"
|
|
59
|
+
>
|
|
60
|
+
<svg class="shell-tabbar-home-icon" aria-hidden="true">
|
|
61
|
+
<use href="{iconsUrl}#house" />
|
|
62
|
+
</svg>
|
|
63
|
+
</button>
|
|
64
|
+
{/if}
|
|
65
|
+
</header>
|
|
66
|
+
|
|
67
|
+
<main class="shell-content" data-shell-region="content" data-shell-layer="0">
|
|
68
|
+
<LayoutRenderer />
|
|
69
|
+
</main>
|
|
70
|
+
|
|
71
|
+
<footer class="shell-statusbar" data-shell-region="statusbar">
|
|
72
|
+
<span class="shell-statusbar-alpha">alpha</span>
|
|
73
|
+
</footer>
|
|
74
|
+
|
|
75
|
+
<!--
|
|
76
|
+
Overlay roots. Each is absolutely positioned over the entire shell with
|
|
77
|
+
pointer-events: none by default; layer managers enable pointer events on
|
|
78
|
+
the specific surfaces they portal in.
|
|
79
|
+
-->
|
|
80
|
+
<div class="shell-overlays" aria-hidden="true">
|
|
81
|
+
{#each overlayLayers as { layer, name } (layer)}
|
|
82
|
+
<div
|
|
83
|
+
class="shell-overlay-root"
|
|
84
|
+
data-shell-overlay={name}
|
|
85
|
+
data-shell-layer={layer}
|
|
86
|
+
style="z-index: var(--shell-z-layer-{layer});"
|
|
87
|
+
bind:this={overlayRoots[name]}
|
|
88
|
+
>
|
|
89
|
+
{#if name === 'drag-preview'}
|
|
90
|
+
<DragPreview />
|
|
91
|
+
{/if}
|
|
92
|
+
</div>
|
|
93
|
+
{/each}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<style>
|
|
98
|
+
.shell {
|
|
99
|
+
display: grid;
|
|
100
|
+
grid-template-rows: var(--shell-tabbar-height) 1fr var(--shell-statusbar-height);
|
|
101
|
+
height: 100%;
|
|
102
|
+
width: 100%;
|
|
103
|
+
position: relative;
|
|
104
|
+
background: var(--shell-bg);
|
|
105
|
+
color: var(--shell-fg);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.shell-tabbar {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: var(--shell-pad-md);
|
|
112
|
+
padding: 0 var(--shell-pad-md);
|
|
113
|
+
background: var(--shell-bg-elevated);
|
|
114
|
+
border-bottom: 1px solid var(--shell-border);
|
|
115
|
+
user-select: none;
|
|
116
|
+
}
|
|
117
|
+
.shell-tabbar-brand {
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
color: var(--shell-accent);
|
|
120
|
+
letter-spacing: 0.5px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.shell-content {
|
|
124
|
+
position: relative;
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
background: var(--shell-bg);
|
|
127
|
+
min-width: 0;
|
|
128
|
+
min-height: 0;
|
|
129
|
+
}
|
|
130
|
+
.shell-statusbar {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: space-between;
|
|
134
|
+
padding: 0 var(--shell-pad-md);
|
|
135
|
+
background: var(--shell-bg-sunken);
|
|
136
|
+
border-top: 1px solid var(--shell-border);
|
|
137
|
+
color: var(--shell-fg-muted);
|
|
138
|
+
font-size: 11px;
|
|
139
|
+
user-select: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.shell-overlays {
|
|
143
|
+
position: absolute;
|
|
144
|
+
inset: 0;
|
|
145
|
+
pointer-events: none;
|
|
146
|
+
}
|
|
147
|
+
.shell-overlay-root {
|
|
148
|
+
position: absolute;
|
|
149
|
+
inset: 0;
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.shell-tabbar-home-button {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
justify-content: center;
|
|
157
|
+
width: 24px;
|
|
158
|
+
height: 24px;
|
|
159
|
+
padding: 0;
|
|
160
|
+
background: transparent;
|
|
161
|
+
color: var(--shell-fg-muted);
|
|
162
|
+
border: 1px solid var(--shell-border);
|
|
163
|
+
border-radius: 4px;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
}
|
|
166
|
+
.shell-tabbar-home-button:hover {
|
|
167
|
+
color: var(--shell-fg);
|
|
168
|
+
border-color: var(--shell-fg-muted);
|
|
169
|
+
}
|
|
170
|
+
.shell-tabbar-home-icon {
|
|
171
|
+
width: 14px;
|
|
172
|
+
height: 14px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.shell-statusbar-alpha {
|
|
176
|
+
font-size: 9px;
|
|
177
|
+
font-weight: 700;
|
|
178
|
+
text-transform: uppercase;
|
|
179
|
+
letter-spacing: 0.08em;
|
|
180
|
+
color: #fff;
|
|
181
|
+
background: var(--shell-accent);
|
|
182
|
+
padding: 1px 6px;
|
|
183
|
+
border-radius: 8px;
|
|
184
|
+
}
|
|
185
|
+
</style>
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { shell } from './shellRuntime.svelte';
|
|
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';
|
|
5
|
+
export type { ZoneSchema, ZoneName } from './state/types';
|
|
6
|
+
export type { StateZones } from './state/zones.svelte';
|
|
7
|
+
export type { App, AppManifest, AppContext } from './apps/types';
|
|
8
|
+
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
9
|
+
export { launchApp, returnToHome } from './apps/lifecycle';
|
|
10
|
+
export { inspectActiveLayout, spliceIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
|
|
11
|
+
export type { DocumentHandle, DocumentHandleOptions, DocumentFormat, DocumentMeta, DocumentChange, AutosaveController, } from './documents/types';
|
|
12
|
+
export { registeredShards, activeShards } from './shards/activate.svelte';
|
|
13
|
+
export type { RegistryIndex, PackageEntry, PackageVersion, RequiredDependency, InstalledPackage, InstallResult, PackageMeta, } from './registry/types';
|
|
14
|
+
export type { ResolvedPackage } from './registry/client';
|
|
15
|
+
export { fetchRegistries, fetchBundle, buildPackageMeta } from './registry/client';
|
|
16
|
+
export { validateRegistryIndex } from './registry/schema';
|
|
17
|
+
export { isAdmin, getAuthHeader } from './auth/index';
|
|
18
|
+
/** Runtime feature flags for target-dependent behavior. */
|
|
19
|
+
export declare const capabilities: {
|
|
20
|
+
/** Whether this target supports hot-installing packages via dynamic import from blob URL. */
|
|
21
|
+
readonly hotInstall: boolean;
|
|
22
|
+
};
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public framework API — the single import path shards and apps are
|
|
3
|
+
* allowed to touch. The phase 9 package boundary turns this file (and
|
|
4
|
+
* only this file) into the published entry point.
|
|
5
|
+
*
|
|
6
|
+
* What belongs here:
|
|
7
|
+
* - Types that shards and apps consume (Shard, App, ShardContext, etc.)
|
|
8
|
+
* - The `shell` runtime singleton (modal/popup/toast)
|
|
9
|
+
* - Layout inspection/mutation helpers for advanced shards (added in
|
|
10
|
+
* Task 12)
|
|
11
|
+
* - Host actions callable from inside views (launchApp, returnToHome,
|
|
12
|
+
* listRegisteredApps — added in Task 7 and Task 10)
|
|
13
|
+
*
|
|
14
|
+
* What does NOT belong here:
|
|
15
|
+
* - `registerShard` / `registerApp` — those are host-only and live in
|
|
16
|
+
* `host.ts`. Shards and apps must not register each other.
|
|
17
|
+
* - Framework internals (state zone plumbing, layout manager internals,
|
|
18
|
+
* shard activation machinery).
|
|
19
|
+
*
|
|
20
|
+
* Anything re-exported from this file is part of the public contract.
|
|
21
|
+
* Phase 10 adds a validator that enforces the import-hygiene rule; for
|
|
22
|
+
* now the convention is documented and honored by hand.
|
|
23
|
+
*/
|
|
24
|
+
// Runtime singleton.
|
|
25
|
+
export { shell } from './shellRuntime.svelte';
|
|
26
|
+
// Host actions callable from inside views (shell home, status bar, etc.).
|
|
27
|
+
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
28
|
+
export { launchApp, returnToHome } from './apps/lifecycle';
|
|
29
|
+
// Layout inspection / mutation for advanced shards (diagnostic, etc.).
|
|
30
|
+
export { inspectActiveLayout, spliceIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
|
|
31
|
+
// Shard introspection — read-only reactive maps exposing which shards are
|
|
32
|
+
// known to the host and which are currently active. Intended for diagnostic
|
|
33
|
+
// and tooling shards that need to visualize framework state. Phase 9
|
|
34
|
+
// addition: diagnostic used to reach `activate.svelte` directly via $lib;
|
|
35
|
+
// the package boundary requires routing through the public surface.
|
|
36
|
+
export { registeredShards, activeShards } from './shards/activate.svelte';
|
|
37
|
+
export { fetchRegistries, fetchBundle, buildPackageMeta } from './registry/client';
|
|
38
|
+
export { validateRegistryIndex } from './registry/schema';
|
|
39
|
+
// Admin mode (framework-internal components read admin status).
|
|
40
|
+
export { isAdmin, getAuthHeader } from './auth/index';
|
|
41
|
+
/** Runtime feature flags for target-dependent behavior. */
|
|
42
|
+
export const capabilities = {
|
|
43
|
+
/** Whether this target supports hot-installing packages via dynamic import from blob URL. */
|
|
44
|
+
hotInstall: typeof Blob !== 'undefined' && typeof URL.createObjectURL === 'function',
|
|
45
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the id of the last-launched app from the user state zone. Used at
|
|
3
|
+
* boot to decide whether to auto-launch. Returns null if the user last
|
|
4
|
+
* returned to home or no app has ever been launched.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readLastApp(): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Launch an app by id. Activates all required shards (idempotent for
|
|
9
|
+
* already-active shards), attaches the app's layout, calls `App.activate`,
|
|
10
|
+
* and switches the rendered root to the app tree.
|
|
11
|
+
*
|
|
12
|
+
* If a different app is already active, it is unloaded first. Launching the
|
|
13
|
+
* same app that is already active is a no-op on shards — it only switches
|
|
14
|
+
* back from the home view if needed.
|
|
15
|
+
*
|
|
16
|
+
* @param id - The `AppManifest.id` of the app to launch. Must be registered.
|
|
17
|
+
* @throws If the app is not registered or a required shard is not registered.
|
|
18
|
+
*/
|
|
19
|
+
export declare function launchApp(id: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Unload an active app. Calls `App.deactivate`, detaches the layout, and
|
|
22
|
+
* deactivates the app's non-self-starting required shards. Switches the
|
|
23
|
+
* rendered root to home. No-op if the app is not currently active.
|
|
24
|
+
*
|
|
25
|
+
* @param id - The `AppManifest.id` of the app to unload.
|
|
26
|
+
*/
|
|
27
|
+
export declare function unloadApp(id: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Return to the shell home view without unloading the active app. The
|
|
30
|
+
* app's shards stay running, its layout proxy stays attached with its
|
|
31
|
+
* refcount hold intact, and its view containers stay alive in the pool.
|
|
32
|
+
* Launching the same app again is a root swap only.
|
|
33
|
+
*
|
|
34
|
+
* Writes `null` to `__shell__:last-app` so reloading the page while on
|
|
35
|
+
* home lands on home, not on the formerly-active app.
|
|
36
|
+
*/
|
|
37
|
+
export declare function returnToHome(): void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* App lifecycle — launch, unload, return-to-home.
|
|
3
|
+
*
|
|
4
|
+
* All three functions operate on the shared apps registry and the layout
|
|
5
|
+
* manager. `launchApp` is public (shell home calls it); `unloadApp` is
|
|
6
|
+
* internal (called from launchApp when replacing a different app);
|
|
7
|
+
* `returnToHome` is public (shell UI calls it).
|
|
8
|
+
*
|
|
9
|
+
* "Last active app" is persisted in the user state zone under the
|
|
10
|
+
* reserved shardId `__shell__:last-app`. The value shape is simply
|
|
11
|
+
* `{ id: string | null }`. Writing happens on launch (id) and on
|
|
12
|
+
* return-to-home (null). Boot reads it to decide whether to auto-launch.
|
|
13
|
+
*/
|
|
14
|
+
import { createStateZones } from '../state/zones.svelte';
|
|
15
|
+
import { activateShard, deactivateShard, registeredShards, } from '../shards/activate.svelte';
|
|
16
|
+
import { attachApp, detachApp, switchToApp, switchToHome, } from '../layout/store.svelte';
|
|
17
|
+
import { activeApp, getRegisteredApp } from './registry.svelte';
|
|
18
|
+
// ---------- last-active-app user zone ------------------------------------
|
|
19
|
+
/**
|
|
20
|
+
* Framework-reserved user-zone slot storing which app to boot into on
|
|
21
|
+
* the next session. Keyed under `__shell__:last-app` to avoid collision
|
|
22
|
+
* with any real shard. Reading/writing uses the same zones machinery as
|
|
23
|
+
* any shard — nothing special.
|
|
24
|
+
*/
|
|
25
|
+
const lastAppState = createStateZones('__shell__:last-app', {
|
|
26
|
+
user: { id: null },
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Read the id of the last-launched app from the user state zone. Used at
|
|
30
|
+
* boot to decide whether to auto-launch. Returns null if the user last
|
|
31
|
+
* returned to home or no app has ever been launched.
|
|
32
|
+
*/
|
|
33
|
+
export function readLastApp() {
|
|
34
|
+
return lastAppState.user.id;
|
|
35
|
+
}
|
|
36
|
+
function writeLastApp(id) {
|
|
37
|
+
lastAppState.user.id = id;
|
|
38
|
+
}
|
|
39
|
+
// ---------- app-context state factories ----------------------------------
|
|
40
|
+
const appContexts = new Map();
|
|
41
|
+
function getOrCreateAppContext(appId) {
|
|
42
|
+
let ctx = appContexts.get(appId);
|
|
43
|
+
if (!ctx) {
|
|
44
|
+
ctx = {
|
|
45
|
+
state: (schema) => createStateZones(`__app__:${appId}`, schema),
|
|
46
|
+
};
|
|
47
|
+
appContexts.set(appId, ctx);
|
|
48
|
+
}
|
|
49
|
+
return ctx;
|
|
50
|
+
}
|
|
51
|
+
// ---------- launch --------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* Launch an app by id. Activates all required shards (idempotent for
|
|
54
|
+
* already-active shards), attaches the app's layout, calls `App.activate`,
|
|
55
|
+
* and switches the rendered root to the app tree.
|
|
56
|
+
*
|
|
57
|
+
* If a different app is already active, it is unloaded first. Launching the
|
|
58
|
+
* same app that is already active is a no-op on shards — it only switches
|
|
59
|
+
* back from the home view if needed.
|
|
60
|
+
*
|
|
61
|
+
* @param id - The `AppManifest.id` of the app to launch. Must be registered.
|
|
62
|
+
* @throws If the app is not registered or a required shard is not registered.
|
|
63
|
+
*/
|
|
64
|
+
export async function launchApp(id) {
|
|
65
|
+
var _a;
|
|
66
|
+
const app = getRegisteredApp(id);
|
|
67
|
+
if (!app) {
|
|
68
|
+
throw new Error(`Cannot launch app "${id}": not registered`);
|
|
69
|
+
}
|
|
70
|
+
// If a different app is already active, unload it first. Relaunching
|
|
71
|
+
// the same app (while it's active) is a no-op on shards and layout;
|
|
72
|
+
// we only swap the rendered root back to 'app' in case the user was
|
|
73
|
+
// on home.
|
|
74
|
+
if (activeApp.id && activeApp.id !== id) {
|
|
75
|
+
unloadApp(activeApp.id);
|
|
76
|
+
}
|
|
77
|
+
else if (activeApp.id === id) {
|
|
78
|
+
switchToApp();
|
|
79
|
+
writeLastApp(id);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Activate every required shard. `activateShard` is idempotent for
|
|
83
|
+
// already-active shards (self-starting shards, or shards shared with
|
|
84
|
+
// a previous app that stayed resident) so this is safe to call
|
|
85
|
+
// unconditionally.
|
|
86
|
+
for (const shardId of app.manifest.requiredShards) {
|
|
87
|
+
if (!registeredShards.has(shardId)) {
|
|
88
|
+
throw new Error(`App "${id}" requires shard "${shardId}" which is not registered`);
|
|
89
|
+
}
|
|
90
|
+
await activateShard(shardId);
|
|
91
|
+
}
|
|
92
|
+
// Attach the layout (creates the workspace-zone proxy with version
|
|
93
|
+
// gate) and run the app's optional activate hook.
|
|
94
|
+
attachApp(app);
|
|
95
|
+
void ((_a = app.activate) === null || _a === void 0 ? void 0 : _a.call(app, getOrCreateAppContext(id)));
|
|
96
|
+
activeApp.id = id;
|
|
97
|
+
switchToApp();
|
|
98
|
+
writeLastApp(id);
|
|
99
|
+
}
|
|
100
|
+
// ---------- unload --------------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Unload an active app. Calls `App.deactivate`, detaches the layout, and
|
|
103
|
+
* deactivates the app's non-self-starting required shards. Switches the
|
|
104
|
+
* rendered root to home. No-op if the app is not currently active.
|
|
105
|
+
*
|
|
106
|
+
* @param id - The `AppManifest.id` of the app to unload.
|
|
107
|
+
*/
|
|
108
|
+
export function unloadApp(id) {
|
|
109
|
+
var _a;
|
|
110
|
+
if (activeApp.id !== id)
|
|
111
|
+
return;
|
|
112
|
+
const app = getRegisteredApp(id);
|
|
113
|
+
if (!app)
|
|
114
|
+
return;
|
|
115
|
+
void ((_a = app.deactivate) === null || _a === void 0 ? void 0 : _a.call(app));
|
|
116
|
+
// Detach layout (releases the refcount holds; pool cleanup runs on
|
|
117
|
+
// the next microtask for any slots that no longer have a renderer).
|
|
118
|
+
// Switch to home first so LayoutRenderer stops reading the app's
|
|
119
|
+
// tree before detachApp drops its references.
|
|
120
|
+
switchToHome();
|
|
121
|
+
detachApp();
|
|
122
|
+
// Deactivate this app's required shards IF no other consumer needs
|
|
123
|
+
// them. Phase 8 has at most one app active at a time, so "no other
|
|
124
|
+
// consumer" reduces to "not self-starting AND not required by any
|
|
125
|
+
// other registered app that happens to already be active" — but we
|
|
126
|
+
// don't run multiple apps, so the only survivors are self-starters.
|
|
127
|
+
// The simple rule: deactivate a required shard unless it was
|
|
128
|
+
// self-starting (has an `autostart` field defined).
|
|
129
|
+
for (const shardId of app.manifest.requiredShards) {
|
|
130
|
+
const shard = registeredShards.get(shardId);
|
|
131
|
+
if (!shard)
|
|
132
|
+
continue;
|
|
133
|
+
if (shard.autostart)
|
|
134
|
+
continue; // self-starter stays running
|
|
135
|
+
deactivateShard(shardId);
|
|
136
|
+
}
|
|
137
|
+
activeApp.id = null;
|
|
138
|
+
appContexts.delete(id);
|
|
139
|
+
}
|
|
140
|
+
// ---------- return to home -----------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* Return to the shell home view without unloading the active app. The
|
|
143
|
+
* app's shards stay running, its layout proxy stays attached with its
|
|
144
|
+
* refcount hold intact, and its view containers stay alive in the pool.
|
|
145
|
+
* Launching the same app again is a root swap only.
|
|
146
|
+
*
|
|
147
|
+
* Writes `null` to `__shell__:last-app` so reloading the page while on
|
|
148
|
+
* home lands on home, not on the formerly-active app.
|
|
149
|
+
*/
|
|
150
|
+
export function returnToHome() {
|
|
151
|
+
switchToHome();
|
|
152
|
+
writeLastApp(null);
|
|
153
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { App, AppManifest } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive map of all registered apps keyed by `AppManifest.id`. Populated
|
|
4
|
+
* once at boot by the host's glob-discovery loop via `registerApp`. A future
|
|
5
|
+
* runtime loader may append entries at runtime.
|
|
6
|
+
*/
|
|
7
|
+
export declare const registeredApps: Map<string, App>;
|
|
8
|
+
/**
|
|
9
|
+
* Reactive slot tracking the currently-active app id. Null when the shell is
|
|
10
|
+
* showing the home screen or no app has been launched yet. Phase 8 allows at
|
|
11
|
+
* most one active app at a time.
|
|
12
|
+
*/
|
|
13
|
+
export declare const activeApp: {
|
|
14
|
+
id: string | null;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Register an app with the framework. Must be called before `launchApp`.
|
|
18
|
+
* Throws if an app with the same id is already registered.
|
|
19
|
+
*
|
|
20
|
+
* @param app - The app module to register.
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerApp(app: App): void;
|
|
23
|
+
/**
|
|
24
|
+
* Reactive snapshot of all registered app manifests. Shell home iterates
|
|
25
|
+
* this to populate its launcher list. Returns only manifests so callers
|
|
26
|
+
* don't reach into app bodies (initialLayout, activate hook) — those are
|
|
27
|
+
* launch-time concerns.
|
|
28
|
+
*/
|
|
29
|
+
export declare function listRegisteredApps(): AppManifest[];
|
|
30
|
+
/** Reactive current-app manifest (or null when none is active). */
|
|
31
|
+
export declare function getActiveApp(): AppManifest | null;
|
|
32
|
+
/**
|
|
33
|
+
* Lookup for framework-internal use — returns the full App (not just the
|
|
34
|
+
* manifest) so lifecycle code can reach into `initialLayout` and the
|
|
35
|
+
* activate hook. Not re-exported through `api.ts`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getRegisteredApp(id: string): App | undefined;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* App registry — reactive record of every registered app, plus the
|
|
3
|
+
* single slot for the currently-active app.
|
|
4
|
+
*
|
|
5
|
+
* Registration is called from the host (main.ts glob loop or a future
|
|
6
|
+
* runtime loader). The shell home view reads `listRegisteredApps()`
|
|
7
|
+
* reactively so newly-registered apps appear in the list without
|
|
8
|
+
* reboot. `activeApp` tracks which app (if any) is currently launched;
|
|
9
|
+
* phase 8 allows at most one.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Reactive map of all registered apps keyed by `AppManifest.id`. Populated
|
|
13
|
+
* once at boot by the host's glob-discovery loop via `registerApp`. A future
|
|
14
|
+
* runtime loader may append entries at runtime.
|
|
15
|
+
*/
|
|
16
|
+
export const registeredApps = $state(new Map());
|
|
17
|
+
/**
|
|
18
|
+
* Reactive slot tracking the currently-active app id. Null when the shell is
|
|
19
|
+
* showing the home screen or no app has been launched yet. Phase 8 allows at
|
|
20
|
+
* most one active app at a time.
|
|
21
|
+
*/
|
|
22
|
+
export const activeApp = $state({ id: null });
|
|
23
|
+
/**
|
|
24
|
+
* Register an app with the framework. Must be called before `launchApp`.
|
|
25
|
+
* Throws if an app with the same id is already registered.
|
|
26
|
+
*
|
|
27
|
+
* @param app - The app module to register.
|
|
28
|
+
*/
|
|
29
|
+
export function registerApp(app) {
|
|
30
|
+
const id = app.manifest.id;
|
|
31
|
+
if (registeredApps.has(id)) {
|
|
32
|
+
throw new Error(`App "${id}" is already registered`);
|
|
33
|
+
}
|
|
34
|
+
registeredApps.set(id, app);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Reactive snapshot of all registered app manifests. Shell home iterates
|
|
38
|
+
* this to populate its launcher list. Returns only manifests so callers
|
|
39
|
+
* don't reach into app bodies (initialLayout, activate hook) — those are
|
|
40
|
+
* launch-time concerns.
|
|
41
|
+
*/
|
|
42
|
+
export function listRegisteredApps() {
|
|
43
|
+
return Array.from(registeredApps.values()).map((a) => a.manifest);
|
|
44
|
+
}
|
|
45
|
+
/** Reactive current-app manifest (or null when none is active). */
|
|
46
|
+
export function getActiveApp() {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const id = activeApp.id;
|
|
49
|
+
if (!id)
|
|
50
|
+
return null;
|
|
51
|
+
return (_b = (_a = registeredApps.get(id)) === null || _a === void 0 ? void 0 : _a.manifest) !== null && _b !== void 0 ? _b : null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Lookup for framework-internal use — returns the full App (not just the
|
|
55
|
+
* manifest) so lifecycle code can reach into `initialLayout` and the
|
|
56
|
+
* activate hook. Not re-exported through `api.ts`.
|
|
57
|
+
*/
|
|
58
|
+
export function getRegisteredApp(id) {
|
|
59
|
+
return registeredApps.get(id);
|
|
60
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { LayoutNode } from '../layout/types';
|
|
2
|
+
import type { ZoneSchema } from '../state/types';
|
|
3
|
+
import type { StateZones } from '../state/zones.svelte';
|
|
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.
|
|
8
|
+
*/
|
|
9
|
+
export interface AppManifest {
|
|
10
|
+
/** Unique app identifier. Must match the app's registration key. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Human-readable display name shown in the launcher. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Semver string for the app package. */
|
|
15
|
+
version: string;
|
|
16
|
+
/**
|
|
17
|
+
* Shard ids this app requires. All required shards are activated at
|
|
18
|
+
* launch time (skipping any already-active from a previous launch or
|
|
19
|
+
* from self-starting). Deactivation at unload deactivates only the
|
|
20
|
+
* shards this app is the last consumer of; shared shards and self-
|
|
21
|
+
* starting shards (diagnostic, __shell__) stay running.
|
|
22
|
+
*/
|
|
23
|
+
requiredShards: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Bump to invalidate persisted layouts. On launch, a persisted blob
|
|
26
|
+
* whose version doesn't match this is discarded and `initialLayout`
|
|
27
|
+
* is used instead. Phase 8 does not migrate — version bumps reset
|
|
28
|
+
* user customization.
|
|
29
|
+
*/
|
|
30
|
+
layoutVersion: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Context object passed to `App.activate`. Provides app-scoped state zones
|
|
34
|
+
* that are namespaced to the app id, preventing collision with any shard
|
|
35
|
+
* that happens to share the same name.
|
|
36
|
+
*/
|
|
37
|
+
export interface AppContext {
|
|
38
|
+
/**
|
|
39
|
+
* App-scoped state zones. The shardId underneath is the app id, so
|
|
40
|
+
* `state({ workspace: { x: 0 } }).workspace.x = 1` persists to
|
|
41
|
+
* `sh3:workspace:<appId>` with no collision risk against any shard
|
|
42
|
+
* of the same name.
|
|
43
|
+
*/
|
|
44
|
+
state<T extends ZoneSchema>(schema: T): StateZones<T>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* An app module. An app is a composition document — it declares required
|
|
48
|
+
* shards and an initial layout blueprint, but does not register views or
|
|
49
|
+
* commands itself. The framework activates the required shards on launch,
|
|
50
|
+
* restores or applies the initial layout, and calls the optional hooks.
|
|
51
|
+
*/
|
|
52
|
+
export interface App {
|
|
53
|
+
/** Static manifest describing the app and its requirements. */
|
|
54
|
+
manifest: AppManifest;
|
|
55
|
+
/** Starting layout tree. Used on first launch or when the persisted layout version mismatches. */
|
|
56
|
+
initialLayout: LayoutNode;
|
|
57
|
+
/** Optional hook called after all required shards are active and the layout is attached. */
|
|
58
|
+
activate?(ctx: AppContext): void | Promise<void>;
|
|
59
|
+
/** Optional hook called before the app's shards are deactivated and the layout is detached. */
|
|
60
|
+
deactivate?(): void | Promise<void>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* App contract — phase 8.
|
|
3
|
+
*
|
|
4
|
+
* An app is a composition document. It declares what shards it needs
|
|
5
|
+
* and what layout to start with, plus an optional activate hook for
|
|
6
|
+
* app-internal startup work. Apps are NOT shards — they do not register
|
|
7
|
+
* contributions, do not have their own views, and do not contribute to
|
|
8
|
+
* other apps' layouts. They compose existing shard views.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|