sh3-core 0.17.2 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Sh3.svelte +59 -4
- package/dist/actions/CommandPalette.svelte +1 -2
- package/dist/actions/listeners.js +12 -1
- package/dist/api.d.ts +4 -0
- package/dist/app/store/storeShard.svelte.js +1 -21
- package/dist/app/store/version.d.ts +11 -0
- package/dist/app/store/version.js +39 -0
- package/dist/app/store/version.test.d.ts +1 -0
- package/dist/app/store/version.test.js +44 -0
- package/dist/apps/lifecycle.d.ts +6 -0
- package/dist/apps/lifecycle.js +5 -2
- package/dist/apps/lifecycle.test.js +30 -0
- package/dist/apps/types.d.ts +12 -0
- package/dist/assets/iconIds.generated.d.ts +1 -1
- package/dist/assets/iconIds.generated.js +5 -0
- package/dist/assets/icons.svg +31 -0
- package/dist/auth/auth.svelte.js +18 -8
- package/dist/auth/types.d.ts +6 -0
- package/dist/chrome/CompactChrome.svelte +54 -20
- package/dist/chrome/CompactChrome.svelte.test.js +112 -5
- package/dist/createShell.d.ts +9 -0
- package/dist/createShell.js +20 -7
- package/dist/createShell.remoteAuth.test.d.ts +1 -0
- package/dist/createShell.remoteAuth.test.js +71 -0
- package/dist/documents/http-backend.js +12 -11
- package/dist/env/client.js +11 -5
- package/dist/files/types.d.ts +106 -0
- package/dist/files/types.js +1 -0
- package/dist/gestures/gestureRegistry.d.ts +6 -0
- package/dist/gestures/gestureRegistry.js +190 -0
- package/dist/gestures/gestureRegistry.test.d.ts +1 -0
- package/dist/gestures/gestureRegistry.test.js +120 -0
- package/dist/gestures/index.d.ts +6 -0
- package/dist/gestures/index.js +12 -0
- package/dist/gestures/pointerClaim.d.ts +7 -0
- package/dist/gestures/pointerClaim.js +36 -0
- package/dist/gestures/pointerClaim.test.d.ts +1 -0
- package/dist/gestures/pointerClaim.test.js +64 -0
- package/dist/gestures/types.d.ts +83 -0
- package/dist/gestures/types.js +1 -0
- package/dist/host-entry.d.ts +1 -0
- package/dist/host-entry.js +1 -0
- package/dist/layout/LayoutRenderer.browser.test.js +15 -3
- package/dist/layout/LayoutRenderer.svelte +16 -3
- package/dist/layout/LayoutRenderer.svelte.d.ts +2 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-3-splitter-drag-updates-split-sizes-when-the-splitter-handle-is-dragged-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-5-splitter-collapse-toggle-toggles-collapsed-i--on-double-click-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-6-fixed-slots-hides-the-collapse-widget-on-a-fixed-pane-but-keeps-it-on-panes-with-a-non-fixed-neighbor-1.png +0 -0
- package/dist/layout/compact/CarouselTabs.svelte +362 -0
- package/dist/layout/compact/CarouselTabs.svelte.d.ts +10 -0
- package/dist/layout/compact/CarouselTabs.svelte.test.d.ts +1 -0
- package/dist/layout/compact/CarouselTabs.svelte.test.js +300 -0
- package/dist/layout/compact/CompactRenderer.svelte +1 -1
- package/dist/layout/compact/CompactRenderer.svelte.test.js +49 -0
- package/dist/layout/compact/derive.js +2 -0
- package/dist/layout/compact/derive.test.js +37 -0
- package/dist/layout/compact/enrichCarousels.d.ts +8 -0
- package/dist/layout/compact/enrichCarousels.js +44 -0
- package/dist/layout/compact/enrichCarousels.test.d.ts +1 -0
- package/dist/layout/compact/enrichCarousels.test.js +88 -0
- package/dist/layout/compact/types.d.ts +3 -0
- package/dist/layout/drag.svelte.js +13 -0
- package/dist/layout/store.schemaVersion.test.js +2 -2
- package/dist/layout/types.d.ts +9 -1
- package/dist/layout/types.js +1 -1
- package/dist/layout/types.test.d.ts +1 -0
- package/dist/layout/types.test.js +26 -0
- package/dist/overlays/ModalFrame.svelte +3 -1
- package/dist/overlays/ModalFrame.svelte.d.ts +1 -0
- package/dist/overlays/floatDismiss.js +5 -0
- package/dist/overlays/focusTrap.d.ts +11 -1
- package/dist/overlays/focusTrap.js +11 -9
- package/dist/overlays/modal.js +1 -0
- package/dist/overlays/popup.js +4 -0
- package/dist/overlays/types.d.ts +9 -0
- package/dist/primitives/Button.svelte +18 -0
- package/dist/primitives/Button.svelte.d.ts +6 -0
- package/dist/primitives/ResizableSplitter.svelte +71 -11
- package/dist/primitives/ResizableSplitter.svelte.d.ts +8 -0
- package/dist/primitives/ResizableSplitter.svelte.test.d.ts +1 -0
- package/dist/primitives/ResizableSplitter.svelte.test.js +74 -0
- package/dist/server-shard/types.d.ts +2 -1
- package/dist/shards/activate.svelte.js +16 -0
- package/dist/shards/ctx-fetch.test.d.ts +1 -0
- package/dist/shards/ctx-fetch.test.js +136 -0
- package/dist/shards/types.d.ts +29 -0
- package/dist/transport/apiFetch.d.ts +1 -0
- package/dist/transport/apiFetch.js +65 -0
- package/dist/transport/apiFetch.test.d.ts +1 -0
- package/dist/transport/apiFetch.test.js +37 -0
- package/dist/transport/authToken.d.ts +2 -0
- package/dist/transport/authToken.js +53 -0
- package/dist/transport/authToken.test.d.ts +1 -0
- package/dist/transport/authToken.test.js +33 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/Sh3.svelte
CHANGED
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
import CompactChrome from './chrome/CompactChrome.svelte';
|
|
30
30
|
import CompactRenderer from './layout/compact/CompactRenderer.svelte';
|
|
31
31
|
import { sh3 } from './sh3Runtime.svelte';
|
|
32
|
+
import { claim, revoke } from './gestures/pointerClaim';
|
|
33
|
+
|
|
34
|
+
let contentEl: HTMLElement | undefined = $state();
|
|
32
35
|
|
|
33
36
|
const authenticated = $derived(isAuthenticated());
|
|
34
37
|
const user = $derived(getUser());
|
|
@@ -63,6 +66,43 @@
|
|
|
63
66
|
const stop = startServerSideStream();
|
|
64
67
|
return stop;
|
|
65
68
|
});
|
|
69
|
+
|
|
70
|
+
// Register left/right edge zones as priority:'edge' pointer claims so that
|
|
71
|
+
// any shard with a priority:'normal' claim automatically beats the shell's
|
|
72
|
+
// edge-swipe gesture (carousel navigation, future side-panel reveals).
|
|
73
|
+
const EDGE_PX = 24;
|
|
74
|
+
$effect(() => {
|
|
75
|
+
const el = contentEl;
|
|
76
|
+
if (!el) return;
|
|
77
|
+
|
|
78
|
+
const edgePointers = new Set<number>();
|
|
79
|
+
|
|
80
|
+
function onPointerDown(e: PointerEvent): void {
|
|
81
|
+
const rect = el.getBoundingClientRect();
|
|
82
|
+
const local = e.clientX - rect.left;
|
|
83
|
+
if (local >= EDGE_PX && local <= rect.width - EDGE_PX) return;
|
|
84
|
+
const granted = claim(e.pointerId, { ownerId: 'sh3:edge', axis: 'x', priority: 'edge', depth: 0 });
|
|
85
|
+
if (granted) edgePointers.add(e.pointerId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onPointerEnd(e: PointerEvent): void {
|
|
89
|
+
if (!edgePointers.has(e.pointerId)) return;
|
|
90
|
+
revoke(e.pointerId, 'sh3:edge');
|
|
91
|
+
edgePointers.delete(e.pointerId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
el.addEventListener('pointerdown', onPointerDown);
|
|
95
|
+
el.addEventListener('pointerup', onPointerEnd);
|
|
96
|
+
el.addEventListener('pointercancel', onPointerEnd);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
el.removeEventListener('pointerdown', onPointerDown);
|
|
100
|
+
el.removeEventListener('pointerup', onPointerEnd);
|
|
101
|
+
el.removeEventListener('pointercancel', onPointerEnd);
|
|
102
|
+
for (const id of edgePointers) revoke(id, 'sh3:edge');
|
|
103
|
+
edgePointers.clear();
|
|
104
|
+
};
|
|
105
|
+
});
|
|
66
106
|
</script>
|
|
67
107
|
|
|
68
108
|
<div class="sh3" data-sh3-viewport={viewportClass}>
|
|
@@ -106,7 +146,7 @@
|
|
|
106
146
|
|
|
107
147
|
<GuestBanner />
|
|
108
148
|
|
|
109
|
-
<main class="sh3-content" data-sh3-region="content" data-sh3-layer="0">
|
|
149
|
+
<main class="sh3-content" data-sh3-region="content" data-sh3-layer="0" bind:this={contentEl}>
|
|
110
150
|
{#if viewportClass === 'desktop'}
|
|
111
151
|
<LayoutRenderer />
|
|
112
152
|
{:else}
|
|
@@ -114,9 +154,11 @@
|
|
|
114
154
|
{/if}
|
|
115
155
|
</main>
|
|
116
156
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
157
|
+
{#if viewportClass === 'desktop'}
|
|
158
|
+
<footer class="sh3-statusbar" data-sh3-region="statusbar">
|
|
159
|
+
<!-- alpha tag moved to Sh3Home title row -->
|
|
160
|
+
</footer>
|
|
161
|
+
{/if}
|
|
120
162
|
|
|
121
163
|
<OverlayRoots />
|
|
122
164
|
|
|
@@ -138,6 +180,19 @@
|
|
|
138
180
|
position: relative;
|
|
139
181
|
background: var(--sh3-grad-bg, var(--sh3-bg));
|
|
140
182
|
color: var(--sh3-fg);
|
|
183
|
+
/* Edge-to-edge platforms (Tauri Android in particular) draw the
|
|
184
|
+
* webview under the system status / navigation bars. env() resolves
|
|
185
|
+
* to 0 elsewhere, so this is a no-op on desktop browsers and
|
|
186
|
+
* windowed Tauri builds. box-sizing on body/html is `content-box`
|
|
187
|
+
* by default, so padding here shrinks the grid area accordingly. */
|
|
188
|
+
box-sizing: border-box;
|
|
189
|
+
padding-top: env(safe-area-inset-top);
|
|
190
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
191
|
+
padding-left: env(safe-area-inset-left);
|
|
192
|
+
padding-right: env(safe-area-inset-right);
|
|
193
|
+
}
|
|
194
|
+
.sh3[data-sh3-viewport='compact'] {
|
|
195
|
+
grid-template-rows: var(--sh3-tabbar-height) auto 1fr;
|
|
141
196
|
}
|
|
142
197
|
|
|
143
198
|
.sh3-tabbar {
|
|
@@ -35,9 +35,8 @@
|
|
|
35
35
|
}
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
|
-
<!-- svelte-ignore a11y_autofocus -->
|
|
39
38
|
<div class="sh3-palette" role="dialog" aria-label="Command palette" aria-modal="true" tabindex="-1" onkeydown={onKeydown}>
|
|
40
|
-
<input class="sh3-palette-input hell-base-input" bind:value={query}
|
|
39
|
+
<input class="sh3-palette-input hell-base-input" bind:value={query} placeholder="Command…" />
|
|
41
40
|
<div class="sh3-palette-list" role="listbox">
|
|
42
41
|
{#each ranked as item, i (item.id)}
|
|
43
42
|
<button
|
|
@@ -349,7 +349,18 @@ export function openPalette(opts) {
|
|
|
349
349
|
}
|
|
350
350
|
},
|
|
351
351
|
onClose: () => handle.close(),
|
|
352
|
-
}, {
|
|
352
|
+
}, {
|
|
353
|
+
dismissOnBackdrop: true,
|
|
354
|
+
// On touch-only devices, autofocusing the palette input pops the
|
|
355
|
+
// on-screen keyboard before the user has expressed intent to type
|
|
356
|
+
// — the toolbar-button entry path expects to tap a result, not
|
|
357
|
+
// type. Discriminant: a touch-only device (no hover, coarse
|
|
358
|
+
// pointer) — desktop browsers and laptops with touchscreens
|
|
359
|
+
// still report `(any-hover: hover)`.
|
|
360
|
+
initialFocus: !(typeof window !== 'undefined'
|
|
361
|
+
&& typeof window.matchMedia === 'function'
|
|
362
|
+
&& window.matchMedia('(hover: none) and (pointer: coarse)').matches),
|
|
363
|
+
});
|
|
353
364
|
if (previousFocus) {
|
|
354
365
|
const innerClose = handle.close;
|
|
355
366
|
handle.close = () => {
|
package/dist/api.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type { EnvState } from './env/types';
|
|
|
19
19
|
export type { App, AppManifest, SourceApp, SourceAppManifest, AppContext, } from './apps/types';
|
|
20
20
|
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
21
21
|
export { launchApp, returnToHome, unregisterApp } from './apps/lifecycle';
|
|
22
|
+
export type { LaunchAppOptions } from './apps/lifecycle';
|
|
23
|
+
export type { FileRef, FileHandlerDescriptor, FileHandlerMatch, FileHandlerHeader, FileHandlerPattern, FileHandlerOpen, } from './files/types';
|
|
22
24
|
export { pushNavEntry } from './navigation';
|
|
23
25
|
export type { NavEntry, NavEntryHandle } from './navigation';
|
|
24
26
|
export { spawnSatellite } from './sh3Api/window';
|
|
@@ -61,6 +63,8 @@ export type { RunVerbOpts, RunVerbResult } from './runtime';
|
|
|
61
63
|
export { registerShellMode } from './shell-shard/registerShellMode';
|
|
62
64
|
export type { ShellModeDescriptor, ShellModeOutput, ShellModeDispatchHandler, ShellModeDispatchInput, ShellModeRunsOn, RichEntryHandle, StreamHandle, } from './shell-shard/contract';
|
|
63
65
|
export { SHELL_MODE_CONTRIBUTION_POINT } from './shell-shard/contract';
|
|
66
|
+
export type { GestureRegistry, GestureHandle } from './gestures';
|
|
67
|
+
export type { GestureType, Axis, ClaimPriority, ClaimEntry, PanEvent, ScrollEvent, ButtonEvent, PanOptions, DragOptions, ButtonOptions, ScrollOptions, } from './gestures/types';
|
|
64
68
|
export { VERSION } from './version';
|
|
65
69
|
export declare const FRAMEWORK_SHARD_IDS: readonly string[];
|
|
66
70
|
export { setTokenOverrides, clearTokenOverrides, getTokenOverrides, } from './theme';
|
|
@@ -26,27 +26,7 @@ import { extractBundlePermissions } from '../../registry/permission-descriptions
|
|
|
26
26
|
import { serverInstallPackage, fetchServerPackages, serverUninstallPackage } from '../../env/client';
|
|
27
27
|
import { VERSION } from '../../version';
|
|
28
28
|
import { installVerb, uninstallVerb, appinfoVerb, updateVerb } from './verbs';
|
|
29
|
-
|
|
30
|
-
* Compare two semver-like version strings.
|
|
31
|
-
* Returns true only if `available` is strictly greater than `installed`.
|
|
32
|
-
* Compares major.minor.patch left-to-right as integers.
|
|
33
|
-
* Non-numeric segments are treated as 0.
|
|
34
|
-
*/
|
|
35
|
-
function isNewerVersion(available, installed) {
|
|
36
|
-
var _a, _b;
|
|
37
|
-
const a = available.split('.').map((s) => parseInt(s, 10) || 0);
|
|
38
|
-
const b = installed.split('.').map((s) => parseInt(s, 10) || 0);
|
|
39
|
-
const len = Math.max(a.length, b.length);
|
|
40
|
-
for (let i = 0; i < len; i++) {
|
|
41
|
-
const av = (_a = a[i]) !== null && _a !== void 0 ? _a : 0;
|
|
42
|
-
const bv = (_b = b[i]) !== null && _b !== void 0 ? _b : 0;
|
|
43
|
-
if (av > bv)
|
|
44
|
-
return true;
|
|
45
|
-
if (av < bv)
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
29
|
+
import { isNewerVersion } from './version';
|
|
50
30
|
/**
|
|
51
31
|
* Pick a version entry from a resolved package. When `requested` is
|
|
52
32
|
* undefined returns `latest`; when set, finds the matching entry by
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two artifact version strings for the app store update check.
|
|
3
|
+
*
|
|
4
|
+
* Handles semver build-metadata suffixes (`+N`): per ADR-013 the build suffix
|
|
5
|
+
* represents artifact iterations of the same release, so `1.0.0+1 > 1.0.0`
|
|
6
|
+
* and `1.0.0+2 > 1.0.0+1`. A canonical release (no suffix) is not considered
|
|
7
|
+
* newer than a suffixed one.
|
|
8
|
+
*
|
|
9
|
+
* Returns true only when `available` is strictly greater than `installed`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isNewerVersion(available: string, installed: string): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two artifact version strings for the app store update check.
|
|
3
|
+
*
|
|
4
|
+
* Handles semver build-metadata suffixes (`+N`): per ADR-013 the build suffix
|
|
5
|
+
* represents artifact iterations of the same release, so `1.0.0+1 > 1.0.0`
|
|
6
|
+
* and `1.0.0+2 > 1.0.0+1`. A canonical release (no suffix) is not considered
|
|
7
|
+
* newer than a suffixed one.
|
|
8
|
+
*
|
|
9
|
+
* Returns true only when `available` is strictly greater than `installed`.
|
|
10
|
+
*/
|
|
11
|
+
export function isNewerVersion(available, installed) {
|
|
12
|
+
var _a, _b;
|
|
13
|
+
const [aRelease = '', aBuild = ''] = available.split('+');
|
|
14
|
+
const [bRelease = '', bBuild = ''] = installed.split('+');
|
|
15
|
+
const a = aRelease.split('.').map((s) => parseInt(s, 10) || 0);
|
|
16
|
+
const b = bRelease.split('.').map((s) => parseInt(s, 10) || 0);
|
|
17
|
+
const len = Math.max(a.length, b.length);
|
|
18
|
+
for (let i = 0; i < len; i++) {
|
|
19
|
+
const av = (_a = a[i]) !== null && _a !== void 0 ? _a : 0;
|
|
20
|
+
const bv = (_b = b[i]) !== null && _b !== void 0 ? _b : 0;
|
|
21
|
+
if (av > bv)
|
|
22
|
+
return true;
|
|
23
|
+
if (av < bv)
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
// Release versions are equal — compare build metadata.
|
|
27
|
+
// absent (canonical) < any suffix; numeric suffix compared numerically.
|
|
28
|
+
if (aBuild === bBuild)
|
|
29
|
+
return false;
|
|
30
|
+
if (aBuild === '')
|
|
31
|
+
return false; // canonical is not newer than suffixed
|
|
32
|
+
if (bBuild === '')
|
|
33
|
+
return true; // suffixed is newer than canonical
|
|
34
|
+
const an = parseInt(aBuild, 10);
|
|
35
|
+
const bn = parseInt(bBuild, 10);
|
|
36
|
+
if (!isNaN(an) && !isNaN(bn))
|
|
37
|
+
return an > bn;
|
|
38
|
+
return aBuild > bBuild; // lexicographic fallback for non-numeric suffixes
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { isNewerVersion } from './version';
|
|
3
|
+
describe('isNewerVersion', () => {
|
|
4
|
+
describe('standard semver (no build metadata)', () => {
|
|
5
|
+
it('returns true when available has higher major', () => {
|
|
6
|
+
expect(isNewerVersion('2.0.0', '1.9.9')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it('returns true when available has higher minor', () => {
|
|
9
|
+
expect(isNewerVersion('0.2.0', '0.1.9')).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
it('returns true when available has higher patch', () => {
|
|
12
|
+
expect(isNewerVersion('0.1.1', '0.1.0')).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
it('returns false when available is lower', () => {
|
|
15
|
+
expect(isNewerVersion('0.1.0', '1.0.0')).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it('returns false when versions are equal', () => {
|
|
18
|
+
expect(isNewerVersion('0.1.0', '0.1.0')).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('build metadata suffix (+N)', () => {
|
|
22
|
+
it('returns true when available has suffix and installed does not', () => {
|
|
23
|
+
expect(isNewerVersion('0.1.0+1', '0.1.0')).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('returns false when available has no suffix and installed does', () => {
|
|
26
|
+
expect(isNewerVersion('0.1.0', '0.1.0+1')).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
it('returns true when available has higher numeric suffix', () => {
|
|
29
|
+
expect(isNewerVersion('0.1.0+2', '0.1.0+1')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
it('returns false when available has lower numeric suffix', () => {
|
|
32
|
+
expect(isNewerVersion('0.1.0+1', '0.1.0+2')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('returns false when suffixes are equal', () => {
|
|
35
|
+
expect(isNewerVersion('0.1.0+1', '0.1.0+1')).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('higher build suffix does not beat higher release version', () => {
|
|
38
|
+
expect(isNewerVersion('0.1.0+99', '0.2.0')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('higher release version beats any build suffix', () => {
|
|
41
|
+
expect(isNewerVersion('0.2.0', '0.1.0+99')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
package/dist/apps/lifecycle.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface LaunchAppOptions {
|
|
|
25
25
|
* "home" view to return to first.
|
|
26
26
|
*/
|
|
27
27
|
skipSwitchToHome?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Arbitrary data passed to the app at launch time. Available as
|
|
30
|
+
* `ctx.args` in `App.activate` and `App.onAppReady`.
|
|
31
|
+
* Ignored on re-entry (resume) — the app's existing context is reused.
|
|
32
|
+
*/
|
|
33
|
+
args?: Record<string, unknown>;
|
|
28
34
|
}
|
|
29
35
|
/**
|
|
30
36
|
* Launch an app by id. Activates all required shards (idempotent for
|
package/dist/apps/lifecycle.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* return-to-home (null). Boot reads it to decide whether to auto-launch.
|
|
13
13
|
*/
|
|
14
14
|
import { createStateZones } from '../state/zones.svelte';
|
|
15
|
+
import { createGestureRegistry } from '../gestures';
|
|
15
16
|
import { activateShard, deactivateShard, getShardContext, registeredShards, } from '../shards/activate.svelte';
|
|
16
17
|
import { attachApp, acquireAppSlotHolds, detachApp, switchToApp, switchToHome, } from '../layout/store.svelte';
|
|
17
18
|
import { activeApp, breadcrumbApp, getRegisteredApp, registeredApps } from './registry.svelte';
|
|
@@ -60,7 +61,7 @@ function resolveLaunchScope() {
|
|
|
60
61
|
var _a;
|
|
61
62
|
return (_a = sessionState.activeProjectId) !== null && _a !== void 0 ? _a : getActiveScopeId();
|
|
62
63
|
}
|
|
63
|
-
function getOrCreateAppContext(appId, scopeId) {
|
|
64
|
+
function getOrCreateAppContext(appId, scopeId, args) {
|
|
64
65
|
var _a;
|
|
65
66
|
let ctx = appContexts.get(appId);
|
|
66
67
|
if (!ctx) {
|
|
@@ -68,10 +69,12 @@ function getOrCreateAppContext(appId, scopeId) {
|
|
|
68
69
|
const scope = scopeId !== null && scopeId !== void 0 ? scopeId : resolveLaunchScope();
|
|
69
70
|
ctx = {
|
|
70
71
|
scopeId: scope,
|
|
72
|
+
args: args !== null && args !== void 0 ? args : {},
|
|
71
73
|
state: (schema) => createStateZones(`__app__:${appId}:scope:${scope}`, schema),
|
|
72
74
|
zones: ((_a = app === null || app === void 0 ? void 0 : app.manifest.permissions) === null || _a === void 0 ? void 0 : _a.includes(PERMISSION_STATE_MANAGE))
|
|
73
75
|
? createZoneManager()
|
|
74
76
|
: undefined,
|
|
77
|
+
gestures: createGestureRegistry(typeof document !== 'undefined' ? document.body : null),
|
|
75
78
|
};
|
|
76
79
|
appContexts.set(appId, ctx);
|
|
77
80
|
}
|
|
@@ -156,7 +159,7 @@ export async function launchApp(id, opts = {}) {
|
|
|
156
159
|
// refcount holds on the app's slots now (pool's factory lookup
|
|
157
160
|
// happens in a microtask from this call).
|
|
158
161
|
acquireAppSlotHolds();
|
|
159
|
-
void ((_f = app.activate) === null || _f === void 0 ? void 0 : _f.call(app, getOrCreateAppContext(id)));
|
|
162
|
+
void ((_f = app.activate) === null || _f === void 0 ? void 0 : _f.call(app, getOrCreateAppContext(id, undefined, opts.args)));
|
|
160
163
|
activeApp.id = id;
|
|
161
164
|
setActiveApp(id, new Set((_g = app.manifest.requiredShards) !== null && _g !== void 0 ? _g : []));
|
|
162
165
|
void loadUserBindings(id).then(setUserBindings);
|
|
@@ -630,3 +630,33 @@ describe('launchApp — scenario D.1 skipLastApp option', () => {
|
|
|
630
630
|
expect(readLastApp()).toBe('app-default-last');
|
|
631
631
|
});
|
|
632
632
|
});
|
|
633
|
+
// ---------------------------------------------------------------------------
|
|
634
|
+
// Launch args — ctx.args
|
|
635
|
+
// ---------------------------------------------------------------------------
|
|
636
|
+
describe('launchApp — ctx.args', () => {
|
|
637
|
+
beforeEach(resetFramework);
|
|
638
|
+
it('passes args to activate via ctx.args', async () => {
|
|
639
|
+
let receivedArgs;
|
|
640
|
+
const shard = makeShard({ manifest: makeShardManifest({ id: 'shard-args' }) });
|
|
641
|
+
registerShard(shard);
|
|
642
|
+
const app = makeApp({
|
|
643
|
+
manifest: makeAppManifest({ id: 'app-args', requiredShards: ['shard-args'] }),
|
|
644
|
+
activate: (ctx) => { receivedArgs = ctx.args; },
|
|
645
|
+
});
|
|
646
|
+
registerApp(app);
|
|
647
|
+
await launchApp('app-args', { args: { file: { path: '/doc.svg', tenantId: 'alice', binary: false } } });
|
|
648
|
+
expect(receivedArgs).toEqual({ file: { path: '/doc.svg', tenantId: 'alice', binary: false } });
|
|
649
|
+
});
|
|
650
|
+
it('defaults ctx.args to empty object when no args passed', async () => {
|
|
651
|
+
let receivedArgs;
|
|
652
|
+
const shard = makeShard({ manifest: makeShardManifest({ id: 'shard-noargs' }) });
|
|
653
|
+
registerShard(shard);
|
|
654
|
+
const app = makeApp({
|
|
655
|
+
manifest: makeAppManifest({ id: 'app-noargs', requiredShards: ['shard-noargs'] }),
|
|
656
|
+
activate: (ctx) => { receivedArgs = ctx.args; },
|
|
657
|
+
});
|
|
658
|
+
registerApp(app);
|
|
659
|
+
await launchApp('app-noargs');
|
|
660
|
+
expect(receivedArgs).toEqual({});
|
|
661
|
+
});
|
|
662
|
+
});
|
package/dist/apps/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
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
|
+
import type { GestureRegistry } from '../gestures';
|
|
4
5
|
/**
|
|
5
6
|
* One menu bar container ("File", "Edit", etc.). Apps declare these in
|
|
6
7
|
* `AppManifest.menus`; when omitted, sh3-core uses DEFAULT_MENU_CONTAINERS.
|
|
@@ -95,6 +96,12 @@ export interface AppContext {
|
|
|
95
96
|
* scope and cannot reach across scopes — exiting a scope unloads the app.
|
|
96
97
|
*/
|
|
97
98
|
scopeId: string;
|
|
99
|
+
/**
|
|
100
|
+
* Arguments supplied by the caller at launch time via `LaunchAppOptions.args`.
|
|
101
|
+
* Defaults to `{}` when the app is launched without explicit args.
|
|
102
|
+
* Read-only — apps observe but do not mutate this object.
|
|
103
|
+
*/
|
|
104
|
+
readonly args: Record<string, unknown>;
|
|
98
105
|
/**
|
|
99
106
|
* App-scoped state zones. The shardId underneath is the app id plus the
|
|
100
107
|
* scope, so `state({ workspace: { x: 0 } }).workspace.x = 1` persists to
|
|
@@ -108,6 +115,11 @@ export interface AppContext {
|
|
|
108
115
|
* `if (ctx.zones)` before use.
|
|
109
116
|
*/
|
|
110
117
|
zones?: ZoneManager;
|
|
118
|
+
/**
|
|
119
|
+
* Per-app gesture binding API. Register Pan, Drag, Button, and Scroll
|
|
120
|
+
* handlers that participate in the shell's pointer arbitration protocol.
|
|
121
|
+
*/
|
|
122
|
+
gestures: GestureRegistry;
|
|
111
123
|
}
|
|
112
124
|
/**
|
|
113
125
|
* An app module. An app is a composition document — it declares required
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const ICON_IDS: readonly ["activity", "align-horizontal-justify-center", "align-horizontal-justify-end", "align-horizontal-justify-start", "app-window", "archive", "archive-restore", "axis-3d", "box", "brick-wall", "bug", "building-2", "cable", "calendar", "camera", "check", "chevron-down", "chevron-right", "circle-check", "circle-dot", "circle-minus", "circle-x", "clipboard", "clipboard-paste", "clock", "compass", "component", "copy", "cpu", "crop", "crosshair", "crown", "dollar-sign", "download", "droplet", "eraser", "euro", "external-link", "eye", "eye-off", "file", "file-archive", "file-diff", "file-plus", "file-text", "flame", "flip-horizontal-2", "flip-vertical-2", "folder", "folder-open", "folder-plus", "folder-tree", "gallery-vertical-end", "gamepad-2", "gauge", "gem", "git-branch", "git-commit-horizontal", "git-merge", "globe", "grid-2x2", "grid-3x3", "group", "hard-drive", "heart", "history", "house", "image", "info", "joystick", "key", "layers", "layout-dashboard", "layout-grid", "layout-list", "layout-panel-left", "layout-panel-top", "layout-template", "lightbulb", "link", "list-ordered", "list-tree", "lock", "log-out", "magnet", "mail", "map", "maximize", "minimize", "moon", "mouse-pointer", "move", "move-3d", "music", "navigation", "network", "notebook-pen", "palette", "pause", "pencil", "pipette", "play", "plus", "pointer", "pound-sterling", "receipt", "redo-2", "refresh-cw", "rocket", "rotate-3d", "rotate-ccw", "rotate-cw", "ruler", "save", "scissors", "scroll-text", "search", "send", "server", "settings", "shield", "skull", "sliders-horizontal", "snowflake", "sparkles", "square", "square-terminal", "star", "sun", "sword", "table-properties", "target", "texture", "timer", "trash-2", "triangle-alert", "type", "undo-2", "ungroup", "unity", "upload", "user", "users", "video", "volume-2", "wand-sparkles", "wind", "x", "zap", "zoom-in", "zoom-out"];
|
|
1
|
+
export declare const ICON_IDS: readonly ["activity", "align-horizontal-justify-center", "align-horizontal-justify-end", "align-horizontal-justify-start", "app-window", "archive", "archive-restore", "axis-3d", "box", "brick-wall", "bug", "building-2", "cable", "calendar", "camera", "check", "chevron-down", "chevron-right", "circle-check", "circle-dot", "circle-minus", "circle-x", "clipboard", "clipboard-paste", "clock", "command", "compass", "component", "copy", "cpu", "crop", "crosshair", "crown", "dollar-sign", "download", "droplet", "ellipsis-vertical", "eraser", "euro", "external-link", "eye", "eye-off", "file", "file-archive", "file-diff", "file-plus", "file-text", "flame", "flip-horizontal-2", "flip-vertical-2", "folder", "folder-open", "folder-plus", "folder-tree", "gallery-vertical-end", "gamepad-2", "gauge", "gem", "git-branch", "git-commit-horizontal", "git-merge", "globe", "grid-2x2", "grid-3x3", "group", "hard-drive", "heart", "history", "house", "image", "info", "joystick", "key", "layers", "layout-dashboard", "layout-grid", "layout-list", "layout-panel-left", "layout-panel-top", "layout-template", "lightbulb", "link", "list-ordered", "list-tree", "lock", "log-out", "magnet", "mail", "map", "maximize", "menu", "minimize", "moon", "mouse-pointer", "move", "move-3d", "music", "navigation", "network", "notebook-pen", "palette", "panel-right", "panel-top", "pause", "pencil", "pipette", "play", "plus", "pointer", "pound-sterling", "receipt", "redo-2", "refresh-cw", "rocket", "rotate-3d", "rotate-ccw", "rotate-cw", "ruler", "save", "scissors", "scroll-text", "search", "send", "server", "settings", "shield", "skull", "sliders-horizontal", "snowflake", "sparkles", "square", "square-terminal", "star", "sun", "sword", "table-properties", "target", "texture", "timer", "trash-2", "triangle-alert", "type", "undo-2", "ungroup", "unity", "upload", "user", "users", "video", "volume-2", "wand-sparkles", "wind", "x", "zap", "zoom-in", "zoom-out"];
|
|
2
2
|
export type IconId = (typeof ICON_IDS)[number];
|
|
@@ -25,6 +25,7 @@ export const ICON_IDS = [
|
|
|
25
25
|
'clipboard',
|
|
26
26
|
'clipboard-paste',
|
|
27
27
|
'clock',
|
|
28
|
+
'command',
|
|
28
29
|
'compass',
|
|
29
30
|
'component',
|
|
30
31
|
'copy',
|
|
@@ -35,6 +36,7 @@ export const ICON_IDS = [
|
|
|
35
36
|
'dollar-sign',
|
|
36
37
|
'download',
|
|
37
38
|
'droplet',
|
|
39
|
+
'ellipsis-vertical',
|
|
38
40
|
'eraser',
|
|
39
41
|
'euro',
|
|
40
42
|
'external-link',
|
|
@@ -88,6 +90,7 @@ export const ICON_IDS = [
|
|
|
88
90
|
'mail',
|
|
89
91
|
'map',
|
|
90
92
|
'maximize',
|
|
93
|
+
'menu',
|
|
91
94
|
'minimize',
|
|
92
95
|
'moon',
|
|
93
96
|
'mouse-pointer',
|
|
@@ -98,6 +101,8 @@ export const ICON_IDS = [
|
|
|
98
101
|
'network',
|
|
99
102
|
'notebook-pen',
|
|
100
103
|
'palette',
|
|
104
|
+
'panel-right',
|
|
105
|
+
'panel-top',
|
|
101
106
|
'pause',
|
|
102
107
|
'pencil',
|
|
103
108
|
'pipette',
|
package/dist/assets/icons.svg
CHANGED
|
@@ -1128,4 +1128,35 @@
|
|
|
1128
1128
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
1129
1129
|
</symbol>
|
|
1130
1130
|
|
|
1131
|
+
<!-- lucide/menu -->
|
|
1132
|
+
<symbol id="menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1133
|
+
<path d="M4 5h16" />
|
|
1134
|
+
<path d="M4 12h16" />
|
|
1135
|
+
<path d="M4 19h16" />
|
|
1136
|
+
</symbol>
|
|
1137
|
+
|
|
1138
|
+
<!-- lucide/panel-right -->
|
|
1139
|
+
<symbol id="panel-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1140
|
+
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
1141
|
+
<path d="M15 3v18" />
|
|
1142
|
+
</symbol>
|
|
1143
|
+
|
|
1144
|
+
<!-- lucide/panel-top -->
|
|
1145
|
+
<symbol id="panel-top" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1146
|
+
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
1147
|
+
<path d="M3 9h18" />
|
|
1148
|
+
</symbol>
|
|
1149
|
+
|
|
1150
|
+
<!-- lucide/command -->
|
|
1151
|
+
<symbol id="command" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1152
|
+
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
|
|
1153
|
+
</symbol>
|
|
1154
|
+
|
|
1155
|
+
<!-- lucide/ellipsis-vertical -->
|
|
1156
|
+
<symbol id="ellipsis-vertical" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1157
|
+
<circle cx="12" cy="12" r="1" />
|
|
1158
|
+
<circle cx="12" cy="5" r="1" />
|
|
1159
|
+
<circle cx="12" cy="19" r="1" />
|
|
1160
|
+
</symbol>
|
|
1161
|
+
|
|
1131
1162
|
</svg>
|
package/dist/auth/auth.svelte.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* .svelte.ts because it uses $state for reactive auth status.
|
|
10
10
|
*/
|
|
11
|
+
import { apiFetch } from '../transport/apiFetch';
|
|
12
|
+
import { setAuthToken } from '../transport/authToken';
|
|
11
13
|
/** Reactive auth state. */
|
|
12
14
|
let currentUser = $state(null);
|
|
13
15
|
let currentSession = $state(null);
|
|
@@ -23,22 +25,28 @@ let authConfig = null;
|
|
|
23
25
|
* after fetching /api/boot.
|
|
24
26
|
*/
|
|
25
27
|
export function initFromBoot(url, config) {
|
|
28
|
+
var _a, _b;
|
|
26
29
|
serverUrl = url;
|
|
27
30
|
authConfig = config.auth;
|
|
28
31
|
currentUser = config.user;
|
|
29
32
|
currentSession = config.session;
|
|
30
33
|
guest = !config.session && !config.user;
|
|
34
|
+
// Sync the cross-origin auth-token store. If the boot returned no
|
|
35
|
+
// session but localStorage still holds a stale token, drop it — the
|
|
36
|
+
// server already disowned it (otherwise it would have echoed the
|
|
37
|
+
// session back).
|
|
38
|
+
setAuthToken((_b = (_a = config.session) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : null);
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
41
|
* Log in with username + password. On success, updates reactive state.
|
|
34
42
|
* Returns { ok: true } or { ok: false, error: string }.
|
|
35
43
|
*/
|
|
36
44
|
export async function login(username, password) {
|
|
45
|
+
var _a, _b;
|
|
37
46
|
try {
|
|
38
|
-
const res = await
|
|
47
|
+
const res = await apiFetch(`${serverUrl}/api/auth/login`, {
|
|
39
48
|
method: 'POST',
|
|
40
49
|
headers: { 'Content-Type': 'application/json' },
|
|
41
|
-
credentials: 'include',
|
|
42
50
|
body: JSON.stringify({ username, password }),
|
|
43
51
|
});
|
|
44
52
|
if (!res.ok) {
|
|
@@ -49,9 +57,10 @@ export async function login(username, password) {
|
|
|
49
57
|
currentUser = body.user;
|
|
50
58
|
currentSession = body.session;
|
|
51
59
|
guest = false;
|
|
60
|
+
setAuthToken((_b = (_a = body.session) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : null);
|
|
52
61
|
return { ok: true };
|
|
53
62
|
}
|
|
54
|
-
catch (
|
|
63
|
+
catch (_c) {
|
|
55
64
|
return { ok: false, error: 'Network error' };
|
|
56
65
|
}
|
|
57
66
|
}
|
|
@@ -60,11 +69,11 @@ export async function login(username, password) {
|
|
|
60
69
|
* On success, auto-logs in and updates reactive state.
|
|
61
70
|
*/
|
|
62
71
|
export async function register(username, password, displayName) {
|
|
72
|
+
var _a, _b;
|
|
63
73
|
try {
|
|
64
|
-
const res = await
|
|
74
|
+
const res = await apiFetch(`${serverUrl}/api/auth/register`, {
|
|
65
75
|
method: 'POST',
|
|
66
76
|
headers: { 'Content-Type': 'application/json' },
|
|
67
|
-
credentials: 'include',
|
|
68
77
|
body: JSON.stringify({ username, password, displayName }),
|
|
69
78
|
});
|
|
70
79
|
if (!res.ok) {
|
|
@@ -75,9 +84,10 @@ export async function register(username, password, displayName) {
|
|
|
75
84
|
currentUser = body.user;
|
|
76
85
|
currentSession = body.session;
|
|
77
86
|
guest = false;
|
|
87
|
+
setAuthToken((_b = (_a = body.session) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : null);
|
|
78
88
|
return { ok: true };
|
|
79
89
|
}
|
|
80
|
-
catch (
|
|
90
|
+
catch (_c) {
|
|
81
91
|
return { ok: false, error: 'Network error' };
|
|
82
92
|
}
|
|
83
93
|
}
|
|
@@ -92,14 +102,14 @@ export async function register(username, password, displayName) {
|
|
|
92
102
|
*/
|
|
93
103
|
export async function logout() {
|
|
94
104
|
try {
|
|
95
|
-
await
|
|
105
|
+
await apiFetch(`${serverUrl}/api/auth/logout`, {
|
|
96
106
|
method: 'POST',
|
|
97
|
-
credentials: 'include',
|
|
98
107
|
});
|
|
99
108
|
}
|
|
100
109
|
catch (_a) {
|
|
101
110
|
// Best effort
|
|
102
111
|
}
|
|
112
|
+
setAuthToken(null);
|
|
103
113
|
if ((authConfig === null || authConfig === void 0 ? void 0 : authConfig.required) && !authConfig.guestAllowed) {
|
|
104
114
|
// Policy forbids guest browsing — re-run the boot-time hard gate.
|
|
105
115
|
// Do not touch reactive state: the page is leaving.
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface BootConfig {
|
|
|
29
29
|
user: AuthUser | null;
|
|
30
30
|
session: AuthSession | null;
|
|
31
31
|
tenantId: string;
|
|
32
|
+
/**
|
|
33
|
+
* Server's `sh3-server` semver. Optional for back-compat with
|
|
34
|
+
* pre-0.18.1 servers that didn't emit it; the cross-origin probe
|
|
35
|
+
* in `Onboarding.svelte` falls back to `'unknown'` when absent.
|
|
36
|
+
*/
|
|
37
|
+
version?: string;
|
|
32
38
|
}
|
|
33
39
|
/** Global settings shape. */
|
|
34
40
|
export interface GlobalSettings {
|