sh3-core 0.1.0 → 0.2.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.
Potentially problematic release.
This version of sh3-core might be problematic. Click here for more details.
- package/dist/api.d.ts +1 -0
- package/dist/assets/icons.svg +1119 -1119
- package/dist/auth/auth.svelte.d.ts +6 -0
- package/dist/auth/auth.svelte.js +8 -0
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/createShell.d.ts +31 -0
- package/dist/createShell.js +109 -0
- package/dist/diagnostic/DiagnosticPanel.svelte +106 -0
- package/dist/diagnostic/DiagnosticPanel.svelte.d.ts +3 -0
- package/dist/diagnostic/DiagnosticPromptModal.svelte +82 -0
- package/dist/diagnostic/DiagnosticPromptModal.svelte.d.ts +8 -0
- package/dist/diagnostic/diagnosticApp.d.ts +2 -0
- package/dist/diagnostic/diagnosticApp.js +24 -0
- package/dist/diagnostic/diagnosticShard.svelte.d.ts +2 -0
- package/dist/diagnostic/diagnosticShard.svelte.js +106 -0
- package/dist/host-entry.d.ts +4 -1
- package/dist/host-entry.js +3 -1
- package/dist/host.d.ts +10 -1
- package/dist/host.js +34 -22
- package/dist/platform/index.d.ts +10 -0
- package/dist/platform/index.js +37 -0
- package/dist/platform/tauri-backend.d.ts +15 -0
- package/dist/platform/tauri-backend.js +58 -0
- package/dist/registry/schema.js +5 -0
- package/dist/registry/types.d.ts +20 -3
- package/dist/server-shard/types.d.ts +67 -0
- package/dist/server-shard/types.js +13 -0
- package/dist/shards/types.d.ts +8 -0
- package/package.json +1 -1
- package/dist/registry-shard/RegistryView.svelte +0 -561
- package/dist/registry-shard/RegistryView.svelte.d.ts +0 -3
- package/dist/registry-shard/registryApp.d.ts +0 -10
- package/dist/registry-shard/registryApp.js +0 -24
- package/dist/registry-shard/registryShard.svelte.d.ts +0 -45
- package/dist/registry-shard/registryShard.svelte.js +0 -125
|
@@ -37,6 +37,12 @@ export declare function deescalate(): void;
|
|
|
37
37
|
* Reactive getter — true when the user has elevated to admin mode.
|
|
38
38
|
*/
|
|
39
39
|
export declare function isAdmin(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Mark this session as local-owner — auto-elevate to admin without
|
|
42
|
+
* key verification. Called by the host in Tauri / dev environments
|
|
43
|
+
* where the user owns the machine. Idempotent.
|
|
44
|
+
*/
|
|
45
|
+
export declare function setLocalOwner(): void;
|
|
40
46
|
/**
|
|
41
47
|
* Build an Authorization header value for authenticated fetch calls.
|
|
42
48
|
* Returns null if not elevated.
|
package/dist/auth/auth.svelte.js
CHANGED
|
@@ -109,6 +109,14 @@ export function deescalate() {
|
|
|
109
109
|
export function isAdmin() {
|
|
110
110
|
return admin;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Mark this session as local-owner — auto-elevate to admin without
|
|
114
|
+
* key verification. Called by the host in Tauri / dev environments
|
|
115
|
+
* where the user owns the machine. Idempotent.
|
|
116
|
+
*/
|
|
117
|
+
export function setLocalOwner() {
|
|
118
|
+
admin = true;
|
|
119
|
+
}
|
|
112
120
|
/**
|
|
113
121
|
* Build an Authorization header value for authenticated fetch calls.
|
|
114
122
|
* Returns null if not elevated.
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { initAuth, elevate, deescalate, isAdmin, getAuthHeader } from './auth.svelte';
|
|
1
|
+
export { initAuth, elevate, deescalate, isAdmin, getAuthHeader, setLocalOwner } from './auth.svelte';
|
package/dist/auth/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { initAuth, elevate, deescalate, isAdmin, getAuthHeader } from './auth.svelte';
|
|
1
|
+
export { initAuth, elevate, deescalate, isAdmin, getAuthHeader, setLocalOwner } from './auth.svelte';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Shard, App } from './index';
|
|
2
|
+
export interface ShellConfig {
|
|
3
|
+
/** Framework shard IDs to exclude (all included by default) */
|
|
4
|
+
excludeShards?: string[];
|
|
5
|
+
/** Framework app IDs to exclude (all included by default) */
|
|
6
|
+
excludeApps?: string[];
|
|
7
|
+
/** Additional shards to register */
|
|
8
|
+
shards?: Shard[];
|
|
9
|
+
/** Additional apps to register */
|
|
10
|
+
apps?: App[];
|
|
11
|
+
/**
|
|
12
|
+
* Packages to pre-install at boot if not already present in IndexedDB.
|
|
13
|
+
* Used for environments that ship certain packages out of the box
|
|
14
|
+
* (e.g. a registry host pre-includes sh3-registry).
|
|
15
|
+
*/
|
|
16
|
+
preinstall?: Array<{
|
|
17
|
+
/** Path to the client bundle file (relative to host root or absolute URL). */
|
|
18
|
+
path: string;
|
|
19
|
+
/** Path to the server bundle file (optional, for shards with server counterparts). */
|
|
20
|
+
serverPath?: string;
|
|
21
|
+
/** Package metadata for the install record. */
|
|
22
|
+
meta: {
|
|
23
|
+
id: string;
|
|
24
|
+
type: 'shard' | 'app';
|
|
25
|
+
version: string;
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
/** Mount target — CSS selector or element (defaults to '#app') */
|
|
29
|
+
target?: string | HTMLElement;
|
|
30
|
+
}
|
|
31
|
+
export declare function createShell(config?: ShellConfig): Promise<void>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* createShell — public factory for booting an SH3 shell.
|
|
3
|
+
*
|
|
4
|
+
* Consumers call this from their own main.ts instead of manually
|
|
5
|
+
* importing registerShard / registerApp / bootstrap / Shell. The
|
|
6
|
+
* factory handles platform detection, registration, bootstrap, and
|
|
7
|
+
* mounting in the correct order.
|
|
8
|
+
*/
|
|
9
|
+
import { mount } from 'svelte';
|
|
10
|
+
import { Shell } from './index';
|
|
11
|
+
import { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner, installPackage, listInstalledPackages, } from './host';
|
|
12
|
+
import { resolvePlatform } from './platform/index';
|
|
13
|
+
export async function createShell(config) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
// 1. Platform detection — must run before bootstrap so state zones
|
|
16
|
+
// hydrate from the right backend, and local-owner environments
|
|
17
|
+
// auto-elevate to admin.
|
|
18
|
+
const platform = await resolvePlatform();
|
|
19
|
+
if (platform.backends) {
|
|
20
|
+
__setBackend('workspace', platform.backends.workspace);
|
|
21
|
+
__setBackend('user', platform.backends.user);
|
|
22
|
+
}
|
|
23
|
+
if (platform.localOwner) {
|
|
24
|
+
setLocalOwner();
|
|
25
|
+
}
|
|
26
|
+
// 1b. Pre-install packages that this host ships out of the box.
|
|
27
|
+
if ((_a = config === null || config === void 0 ? void 0 : config.preinstall) === null || _a === void 0 ? void 0 : _a.length) {
|
|
28
|
+
const installed = await listInstalledPackages();
|
|
29
|
+
const installedIds = new Set(installed.map((p) => p.id));
|
|
30
|
+
for (const pkg of config.preinstall) {
|
|
31
|
+
if (installedIds.has(pkg.meta.id))
|
|
32
|
+
continue;
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(pkg.path);
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
console.warn(`[sh3] Pre-install fetch failed for "${pkg.meta.id}": HTTP ${res.status}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const bundle = await res.arrayBuffer();
|
|
40
|
+
const result = await installPackage(bundle, {
|
|
41
|
+
id: pkg.meta.id,
|
|
42
|
+
type: pkg.meta.type,
|
|
43
|
+
version: pkg.meta.version,
|
|
44
|
+
contractVersion: '1',
|
|
45
|
+
sourceRegistry: 'local',
|
|
46
|
+
integrity: '',
|
|
47
|
+
hasServerBundle: !!pkg.serverPath,
|
|
48
|
+
});
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
console.warn(`[sh3] Pre-install failed for "${pkg.meta.id}":`, result.error);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Push server bundle if present
|
|
54
|
+
if (pkg.serverPath) {
|
|
55
|
+
try {
|
|
56
|
+
const serverRes = await fetch(pkg.serverPath);
|
|
57
|
+
if (serverRes.ok) {
|
|
58
|
+
const serverBytes = await serverRes.arrayBuffer();
|
|
59
|
+
const form = new FormData();
|
|
60
|
+
form.append('shardId', pkg.meta.id);
|
|
61
|
+
form.append('bundle', new Blob([serverBytes], { type: 'application/javascript' }), 'bundle.js');
|
|
62
|
+
form.append('manifest', JSON.stringify(pkg.meta));
|
|
63
|
+
await fetch('/api/server-shards/install', {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
body: form,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (_c) {
|
|
70
|
+
console.warn(`[sh3] Server bundle push failed for pre-install "${pkg.meta.id}"`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
console.log(`[sh3] Pre-installed: ${pkg.meta.id}`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.warn(`[sh3] Pre-install error for "${pkg.meta.id}":`, err instanceof Error ? err.message : err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// 2. Register consumer-provided shards and apps. These go in before
|
|
81
|
+
// bootstrap() so they appear in registeredShards, but framework
|
|
82
|
+
// shards activate first (insertion-order guarantee in bootstrap).
|
|
83
|
+
if (config === null || config === void 0 ? void 0 : config.shards) {
|
|
84
|
+
for (const shard of config.shards) {
|
|
85
|
+
registerShard(shard);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (config === null || config === void 0 ? void 0 : config.apps) {
|
|
89
|
+
for (const app of config.apps) {
|
|
90
|
+
registerApp(app);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// 3. Bootstrap — framework shards/apps registered internally,
|
|
94
|
+
// filtered by the exclude lists.
|
|
95
|
+
const bootstrapConfig = {};
|
|
96
|
+
if (config === null || config === void 0 ? void 0 : config.excludeShards)
|
|
97
|
+
bootstrapConfig.excludeShards = config.excludeShards;
|
|
98
|
+
if (config === null || config === void 0 ? void 0 : config.excludeApps)
|
|
99
|
+
bootstrapConfig.excludeApps = config.excludeApps;
|
|
100
|
+
await bootstrap(bootstrapConfig);
|
|
101
|
+
// 4. Mount the shell.
|
|
102
|
+
const target = typeof (config === null || config === void 0 ? void 0 : config.target) === 'string'
|
|
103
|
+
? document.querySelector(config.target)
|
|
104
|
+
: (_b = config === null || config === void 0 ? void 0 : config.target) !== null && _b !== void 0 ? _b : document.getElementById('app');
|
|
105
|
+
if (!target) {
|
|
106
|
+
throw new Error('SH3: mount target not found');
|
|
107
|
+
}
|
|
108
|
+
mount(Shell, { target });
|
|
109
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Diagnostic panel — framework introspection view.
|
|
4
|
+
*
|
|
5
|
+
* Shows:
|
|
6
|
+
* - Registered shards (id, version, view count)
|
|
7
|
+
* - Active shards (id)
|
|
8
|
+
* - Active app (id) or "none"
|
|
9
|
+
* - Active layout source (home vs app)
|
|
10
|
+
*
|
|
11
|
+
* Reads through the public sh3 API surface only.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
listRegisteredApps,
|
|
16
|
+
getActiveApp,
|
|
17
|
+
inspectActiveLayout,
|
|
18
|
+
registeredShards,
|
|
19
|
+
activeShards,
|
|
20
|
+
} from '../api';
|
|
21
|
+
|
|
22
|
+
const apps = $derived(listRegisteredApps());
|
|
23
|
+
const active = $derived(getActiveApp());
|
|
24
|
+
const layout = $derived(inspectActiveLayout());
|
|
25
|
+
const regShards = $derived(Array.from(registeredShards.values()));
|
|
26
|
+
const actShards = $derived(Array.from(activeShards.keys()));
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class="diagnostic">
|
|
30
|
+
<h2>Diagnostic</h2>
|
|
31
|
+
|
|
32
|
+
<section>
|
|
33
|
+
<h3>Active app</h3>
|
|
34
|
+
<p>{active ? `${active.label} (${active.id})` : 'none'}</p>
|
|
35
|
+
</section>
|
|
36
|
+
|
|
37
|
+
<section>
|
|
38
|
+
<h3>Layout source</h3>
|
|
39
|
+
<p>{layout.source}</p>
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section>
|
|
43
|
+
<h3>Registered shards ({regShards.length})</h3>
|
|
44
|
+
<ul>
|
|
45
|
+
{#each regShards as shard (shard.manifest.id)}
|
|
46
|
+
<li>
|
|
47
|
+
{shard.manifest.label} — {shard.manifest.id} — v{shard.manifest.version}
|
|
48
|
+
— {shard.manifest.views.length} views
|
|
49
|
+
</li>
|
|
50
|
+
{/each}
|
|
51
|
+
</ul>
|
|
52
|
+
</section>
|
|
53
|
+
|
|
54
|
+
<section>
|
|
55
|
+
<h3>Active shards ({actShards.length})</h3>
|
|
56
|
+
<ul>
|
|
57
|
+
{#each actShards as id (id)}
|
|
58
|
+
<li>{id}</li>
|
|
59
|
+
{/each}
|
|
60
|
+
</ul>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section>
|
|
64
|
+
<h3>Registered apps ({apps.length})</h3>
|
|
65
|
+
<ul>
|
|
66
|
+
{#each apps as manifest (manifest.id)}
|
|
67
|
+
<li>{manifest.label} — {manifest.id} — v{manifest.version}</li>
|
|
68
|
+
{/each}
|
|
69
|
+
</ul>
|
|
70
|
+
</section>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<style>
|
|
74
|
+
.diagnostic {
|
|
75
|
+
position: absolute;
|
|
76
|
+
inset: 0;
|
|
77
|
+
padding: 12px 16px;
|
|
78
|
+
overflow: auto;
|
|
79
|
+
background: var(--shell-bg);
|
|
80
|
+
color: var(--shell-fg);
|
|
81
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
}
|
|
84
|
+
h2 {
|
|
85
|
+
margin: 0 0 12px;
|
|
86
|
+
color: var(--shell-accent);
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
}
|
|
89
|
+
h3 {
|
|
90
|
+
margin: 12px 0 4px;
|
|
91
|
+
color: var(--shell-fg-muted);
|
|
92
|
+
font-size: 11px;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
letter-spacing: 0.5px;
|
|
95
|
+
}
|
|
96
|
+
p, li {
|
|
97
|
+
margin: 0;
|
|
98
|
+
}
|
|
99
|
+
ul {
|
|
100
|
+
margin: 0;
|
|
101
|
+
padding-left: 16px;
|
|
102
|
+
}
|
|
103
|
+
section {
|
|
104
|
+
margin-bottom: 8px;
|
|
105
|
+
}
|
|
106
|
+
</style>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* DiagnosticPromptModal — first-time prompt asking the user whether the
|
|
4
|
+
* diagnostic panel should dock into the current app's layout or stay
|
|
5
|
+
* silent for this session. The choice is persisted per-app-id in the
|
|
6
|
+
* diagnostic shard's workspace zone.
|
|
7
|
+
*
|
|
8
|
+
* Props:
|
|
9
|
+
* - appLabel: human label of the active app (for the prompt text)
|
|
10
|
+
* - onChoose: callback invoked with the user's choice; also responsible
|
|
11
|
+
* for any side effects (persist + dock). We call `close()` right
|
|
12
|
+
* after so the modal tears down regardless.
|
|
13
|
+
*
|
|
14
|
+
* The `close` prop is injected by the modal manager (see overlays/modal.ts).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
appLabel,
|
|
19
|
+
onChoose,
|
|
20
|
+
close,
|
|
21
|
+
}: {
|
|
22
|
+
appLabel: string;
|
|
23
|
+
onChoose: (choice: 'dock' | 'silent') => void;
|
|
24
|
+
close: () => void;
|
|
25
|
+
} = $props();
|
|
26
|
+
|
|
27
|
+
function pick(choice: 'dock' | 'silent') {
|
|
28
|
+
onChoose(choice);
|
|
29
|
+
close();
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class="body">
|
|
34
|
+
<h2>Diagnostic shard</h2>
|
|
35
|
+
<p>
|
|
36
|
+
Dock the diagnostic panel into <strong>{appLabel}</strong>'s layout,
|
|
37
|
+
or keep it silent for this session? Your choice is remembered per app.
|
|
38
|
+
</p>
|
|
39
|
+
<div class="row">
|
|
40
|
+
<button type="button" onclick={() => pick('dock')}>Dock</button>
|
|
41
|
+
<button type="button" class="secondary" onclick={() => pick('silent')}>Silent</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<style>
|
|
46
|
+
.body {
|
|
47
|
+
padding: var(--shell-pad-lg);
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
gap: var(--shell-pad-md);
|
|
51
|
+
min-width: 320px;
|
|
52
|
+
}
|
|
53
|
+
h2 {
|
|
54
|
+
margin: 0;
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
color: var(--shell-fg);
|
|
57
|
+
}
|
|
58
|
+
p {
|
|
59
|
+
margin: 0;
|
|
60
|
+
color: var(--shell-fg-muted);
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
line-height: 1.5;
|
|
63
|
+
}
|
|
64
|
+
.row {
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: var(--shell-pad-sm);
|
|
67
|
+
}
|
|
68
|
+
button {
|
|
69
|
+
appearance: none;
|
|
70
|
+
font: inherit;
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
padding: var(--shell-pad-sm) var(--shell-pad-md);
|
|
73
|
+
background: var(--shell-accent-muted);
|
|
74
|
+
color: var(--shell-fg);
|
|
75
|
+
border: 1px solid var(--shell-border-strong);
|
|
76
|
+
border-radius: 3px;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
button:hover { background: var(--shell-accent); }
|
|
80
|
+
button.secondary { background: transparent; }
|
|
81
|
+
button.secondary:hover { background: var(--shell-bg-sunken); }
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
appLabel: string;
|
|
3
|
+
onChoose: (choice: 'dock' | 'silent') => void;
|
|
4
|
+
close: () => void;
|
|
5
|
+
};
|
|
6
|
+
declare const DiagnosticPromptModal: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type DiagnosticPromptModal = ReturnType<typeof DiagnosticPromptModal>;
|
|
8
|
+
export default DiagnosticPromptModal;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Diagnostic app — framework-shipped introspection app.
|
|
3
|
+
*
|
|
4
|
+
* Provides a launchable app from the home screen that opens the
|
|
5
|
+
* diagnostic panel in a single-tab layout. Unlike the shard's
|
|
6
|
+
* autostart dock behavior (which splices into other apps), this
|
|
7
|
+
* gives the diagnostic view its own dedicated workspace.
|
|
8
|
+
*/
|
|
9
|
+
export const diagnosticApp = {
|
|
10
|
+
manifest: {
|
|
11
|
+
id: 'diagnostic-app',
|
|
12
|
+
label: 'Diagnostic',
|
|
13
|
+
version: '0.1.0',
|
|
14
|
+
requiredShards: ['diagnostic'],
|
|
15
|
+
layoutVersion: 1,
|
|
16
|
+
},
|
|
17
|
+
initialLayout: {
|
|
18
|
+
type: 'tabs',
|
|
19
|
+
activeTab: 0,
|
|
20
|
+
tabs: [
|
|
21
|
+
{ slotId: 'diagnostic.main', viewId: 'diagnostic:panel', label: 'Diagnostic' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Diagnostic shard — self-driving framework introspection shard.
|
|
3
|
+
*
|
|
4
|
+
* Always loaded in phase 8 (no env split yet). Self-starts via its
|
|
5
|
+
* `autostart` hook:
|
|
6
|
+
* - If the shell is on home at boot: silently attempt to splice a
|
|
7
|
+
* diagnostic panel into home's layout. Home is a single-slot layout
|
|
8
|
+
* in phase 8, so this gracefully no-ops when no tabs group is found.
|
|
9
|
+
* - If an app is active: read own workspace zone for a per-app
|
|
10
|
+
* `sessionBehavior` preference. If 'silent', do nothing. If 'dock',
|
|
11
|
+
* attempt dock. If unset, open a modal asking "Dock" vs "Silent".
|
|
12
|
+
* Persist the choice and act on it.
|
|
13
|
+
*
|
|
14
|
+
* Note on internal imports: DiagnosticPanel.svelte reads from
|
|
15
|
+
* `shards/activate.svelte.ts` directly for the registeredShards /
|
|
16
|
+
* activeShards reactive maps. That's a deliberate phase-8 shortcut —
|
|
17
|
+
* introspection helpers belong on the public api.ts surface long-term,
|
|
18
|
+
* but factoring them out is a phase-9 concern.
|
|
19
|
+
*
|
|
20
|
+
* Note on the modal path: in phase 8 all shards load at boot, so
|
|
21
|
+
* autostart runs while the shell is on home. The per-app modal prompt
|
|
22
|
+
* path is therefore effectively dead code in phase 8 (it would only be
|
|
23
|
+
* reachable if diagnostic were activated mid-session after an app had
|
|
24
|
+
* launched). Included for design completeness.
|
|
25
|
+
*/
|
|
26
|
+
import { mount, unmount } from 'svelte';
|
|
27
|
+
import DiagnosticPanel from './DiagnosticPanel.svelte';
|
|
28
|
+
import DiagnosticPromptModal from './DiagnosticPromptModal.svelte';
|
|
29
|
+
import { shell, getActiveApp, spliceIntoActiveLayout, inspectActiveLayout, } from '../api';
|
|
30
|
+
export const diagnosticShard = {
|
|
31
|
+
manifest: {
|
|
32
|
+
id: 'diagnostic',
|
|
33
|
+
label: 'Diagnostic',
|
|
34
|
+
version: '0.1.0',
|
|
35
|
+
views: [{ id: 'diagnostic:panel', label: 'Diagnostic' }],
|
|
36
|
+
},
|
|
37
|
+
activate(ctx) {
|
|
38
|
+
const factory = {
|
|
39
|
+
mount(container, _context) {
|
|
40
|
+
const instance = mount(DiagnosticPanel, { target: container });
|
|
41
|
+
return {
|
|
42
|
+
unmount() {
|
|
43
|
+
unmount(instance);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
ctx.registerView('diagnostic:panel', factory);
|
|
49
|
+
},
|
|
50
|
+
autostart(ctx) {
|
|
51
|
+
const state = ctx.state({
|
|
52
|
+
workspace: {
|
|
53
|
+
sessionBehavior: {},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const active = getActiveApp();
|
|
57
|
+
// Home context: attempt a silent dock. Home's single-slot layout has
|
|
58
|
+
// no tabs group in phase 8 so tryDock will no-op gracefully.
|
|
59
|
+
if (!active) {
|
|
60
|
+
tryDock();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const contextKey = active.id;
|
|
64
|
+
const stored = state.workspace.sessionBehavior[contextKey];
|
|
65
|
+
if (stored === 'silent')
|
|
66
|
+
return;
|
|
67
|
+
if (stored === 'dock') {
|
|
68
|
+
tryDock();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// First-time prompt for this app.
|
|
72
|
+
shell.modal.open(DiagnosticPromptModal, {
|
|
73
|
+
appLabel: active.label,
|
|
74
|
+
onChoose: (choice) => {
|
|
75
|
+
state.workspace.sessionBehavior[contextKey] = choice;
|
|
76
|
+
if (choice === 'dock')
|
|
77
|
+
tryDock();
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
function tryDock() {
|
|
83
|
+
const { root } = inspectActiveLayout();
|
|
84
|
+
if (!containsTabs(root))
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
spliceIntoActiveLayout({
|
|
88
|
+
slotId: 'diagnostic.panel',
|
|
89
|
+
viewId: 'diagnostic:panel',
|
|
90
|
+
label: 'Diagnostic',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Splice can still refuse for reasons the containsTabs probe doesn't
|
|
95
|
+
// cover (e.g. a layout shape the helper doesn't accept). Failing
|
|
96
|
+
// silently is the right call for a self-starting introspection shard.
|
|
97
|
+
console.warn('[diagnostic] splice failed:', err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function containsTabs(node) {
|
|
101
|
+
if (node.type === 'tabs')
|
|
102
|
+
return true;
|
|
103
|
+
if (node.type === 'split')
|
|
104
|
+
return node.children.some((c) => containsTabs(c));
|
|
105
|
+
return false;
|
|
106
|
+
}
|
package/dist/host-entry.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { registerShard, registerApp, bootstrap, __setBackend } from './host';
|
|
1
|
+
export { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner } from './host';
|
|
2
|
+
export type { BootstrapConfig } from './host';
|
|
2
3
|
export { __setTenantId, __setDocumentBackend } from './host';
|
|
3
4
|
export type { Backend } from './state/types';
|
|
4
5
|
export type { DocumentBackend } from './documents/types';
|
|
@@ -7,3 +8,5 @@ export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledP
|
|
|
7
8
|
export type { InstalledPackage, InstallResult, PackageMeta } from './registry/types';
|
|
8
9
|
export { initAuth, elevate, deescalate } from './auth/index';
|
|
9
10
|
export { adminAppIds } from './host';
|
|
11
|
+
export { createShell } from './createShell';
|
|
12
|
+
export type { ShellConfig } from './createShell';
|
package/dist/host-entry.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* boots an SH3 shell (a main.ts that mounts Shell and registers shards/apps)
|
|
6
6
|
* should touch this path. Shards and apps must not import from here.
|
|
7
7
|
*/
|
|
8
|
-
export { registerShard, registerApp, bootstrap, __setBackend } from './host';
|
|
8
|
+
export { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner } from './host';
|
|
9
9
|
export { __setTenantId, __setDocumentBackend } from './host';
|
|
10
10
|
export { HttpDocumentBackend } from './documents/http-backend';
|
|
11
11
|
// Install API (host-only).
|
|
@@ -13,3 +13,5 @@ export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledP
|
|
|
13
13
|
// Admin mode (host-only — elevate/deescalate drive the shell UI, initAuth runs at boot).
|
|
14
14
|
export { initAuth, elevate, deescalate } from './auth/index';
|
|
15
15
|
export { adminAppIds } from './host';
|
|
16
|
+
// Shell boot factory.
|
|
17
|
+
export { createShell } from './createShell';
|
package/dist/host.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { registerShard as registerShardInternal } from './shards/activate.svelte';
|
|
2
2
|
import { registerApp } from './apps/registry.svelte';
|
|
3
3
|
import { __setBackend } from './state/zones.svelte';
|
|
4
|
+
import { setLocalOwner } from './auth/index';
|
|
4
5
|
export { __setBackend };
|
|
6
|
+
export { setLocalOwner };
|
|
5
7
|
export { __setTenantId, __setDocumentBackend } from './documents/config';
|
|
6
8
|
export declare function registerShard(shard: Parameters<typeof registerShardInternal>[0]): void;
|
|
7
9
|
export { registerApp };
|
|
8
|
-
export
|
|
10
|
+
export interface BootstrapConfig {
|
|
11
|
+
/** Framework shard IDs to skip registration for */
|
|
12
|
+
excludeShards?: string[];
|
|
13
|
+
/** Framework app IDs to skip registration for */
|
|
14
|
+
excludeApps?: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function bootstrap(config?: BootstrapConfig): Promise<void>;
|
|
9
17
|
/**
|
|
10
18
|
* Set of app IDs that require admin mode. Framework-internal — used by
|
|
11
19
|
* the shell home to gate visibility. Not part of the app contract.
|
|
12
20
|
*/
|
|
13
21
|
export declare const adminAppIds: ReadonlySet<string>;
|
|
22
|
+
export { installPackage, listInstalledPackages } from './registry/installer';
|
package/dist/host.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registration APIs (`registerShard`, `registerApp`) are data-only and
|
|
5
5
|
* safe to call at any time; a future runtime loader uses them identically
|
|
6
|
-
* to
|
|
6
|
+
* to hot-install registration at runtime.
|
|
7
7
|
*
|
|
8
8
|
* `bootstrap()` runs the post-registration boot sequence: it registers
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* home.
|
|
9
|
+
* framework-owned shards and apps (filtering any the host excludes via
|
|
10
|
+
* BootstrapConfig), walks the registered-shards map and activates every
|
|
11
|
+
* self-starting shard, then reads the last-app user-zone entry and
|
|
12
|
+
* either launches that app or leaves the shell on home.
|
|
13
13
|
*
|
|
14
14
|
* This file is intentionally NOT re-exported through `api.ts`. The
|
|
15
15
|
* import-hygiene rule is: shards and apps import from `api.ts`, the host
|
|
@@ -22,30 +22,41 @@ import { shellShard } from './shell-shard/shellShard.svelte';
|
|
|
22
22
|
import { storeShard } from './store/storeShard.svelte';
|
|
23
23
|
import { __setBackend } from './state/zones.svelte';
|
|
24
24
|
import { loadInstalledPackages } from './registry/installer';
|
|
25
|
-
import { initAuth } from './auth/index';
|
|
25
|
+
import { initAuth, isAdmin, setLocalOwner } from './auth/index';
|
|
26
26
|
import { storeApp } from './store/storeApp';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
27
|
+
import { diagnosticShard } from './diagnostic/diagnosticShard.svelte';
|
|
28
|
+
import { diagnosticApp } from './diagnostic/diagnosticApp';
|
|
29
29
|
export { __setBackend };
|
|
30
|
+
export { setLocalOwner };
|
|
30
31
|
export { __setTenantId, __setDocumentBackend } from './documents/config';
|
|
31
32
|
export function registerShard(shard) {
|
|
32
33
|
registerShardInternal(shard);
|
|
33
34
|
}
|
|
34
35
|
export { registerApp };
|
|
35
|
-
export async function bootstrap() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
export async function bootstrap(config) {
|
|
37
|
+
const exShards = new Set(config === null || config === void 0 ? void 0 : config.excludeShards);
|
|
38
|
+
const exApps = new Set(config === null || config === void 0 ? void 0 : config.excludeApps);
|
|
39
|
+
// 1. Framework-owned shards — registered first so installed packages
|
|
40
|
+
// cannot claim reserved IDs like __shell__ or sh3-store.
|
|
41
|
+
const frameworkShards = [shellShard, storeShard, diagnosticShard];
|
|
42
|
+
for (const shard of frameworkShards) {
|
|
43
|
+
if (!exShards.has(shard.manifest.id)) {
|
|
44
|
+
registerShardInternal(shard);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
42
47
|
// 2. Framework-shipped admin apps.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
const frameworkApps = [storeApp, diagnosticApp];
|
|
49
|
+
for (const app of frameworkApps) {
|
|
50
|
+
if (!exApps.has(app.manifest.id)) {
|
|
51
|
+
registerApp(app);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 3. Auth — if the host already called setLocalOwner() (Tauri / dev),
|
|
55
|
+
// skip server verification. Otherwise verify the stored admin key
|
|
56
|
+
// against the server (3s timeout, fails open).
|
|
57
|
+
if (!isAdmin()) {
|
|
58
|
+
await initAuth();
|
|
59
|
+
}
|
|
49
60
|
// 4. Load any packages that were hot-installed in a previous session
|
|
50
61
|
// from IndexedDB. Runs after framework shards but before activation
|
|
51
62
|
// so installed packages participate in the self-starting pass.
|
|
@@ -70,4 +81,5 @@ export async function bootstrap() {
|
|
|
70
81
|
* Set of app IDs that require admin mode. Framework-internal — used by
|
|
71
82
|
* the shell home to gate visibility. Not part of the app contract.
|
|
72
83
|
*/
|
|
73
|
-
export const adminAppIds = new Set(['sh3-store-app', '
|
|
84
|
+
export const adminAppIds = new Set(['sh3-store-app', 'diagnostic-app']);
|
|
85
|
+
export { installPackage, listInstalledPackages } from './registry/installer';
|