sh3-core 0.10.4 → 0.11.2
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 +12 -31
- package/dist/__test__/fixtures.js +1 -0
- package/dist/__test__/reset.js +6 -0
- package/dist/actions/CommandPalette.svelte +68 -0
- package/dist/actions/CommandPalette.svelte.d.ts +11 -0
- package/dist/actions/ContextMenu.svelte +97 -0
- package/dist/actions/ContextMenu.svelte.d.ts +9 -0
- package/dist/actions/bindings-store.d.ts +8 -0
- package/dist/actions/bindings-store.js +27 -0
- package/dist/actions/bindings-store.test.d.ts +1 -0
- package/dist/actions/bindings-store.test.js +25 -0
- package/dist/actions/bindings.d.ts +4 -0
- package/dist/actions/bindings.js +17 -0
- package/dist/actions/bindings.test.d.ts +1 -0
- package/dist/actions/bindings.test.js +30 -0
- package/dist/actions/contextMenuModel.d.ts +16 -0
- package/dist/actions/contextMenuModel.js +71 -0
- package/dist/actions/contextMenuModel.test.d.ts +1 -0
- package/dist/actions/contextMenuModel.test.js +44 -0
- package/dist/actions/dispatcher.svelte.d.ts +34 -0
- package/dist/actions/dispatcher.svelte.js +117 -0
- package/dist/actions/dispatcher.test.d.ts +1 -0
- package/dist/actions/dispatcher.test.js +155 -0
- package/dist/actions/listeners.d.ts +11 -0
- package/dist/actions/listeners.js +180 -0
- package/dist/actions/listeners.test.d.ts +1 -0
- package/dist/actions/listeners.test.js +149 -0
- package/dist/actions/palette-scorer.d.ts +11 -0
- package/dist/actions/palette-scorer.js +49 -0
- package/dist/actions/palette-scorer.test.d.ts +1 -0
- package/dist/actions/palette-scorer.test.js +40 -0
- package/dist/actions/paletteModel.d.ts +4 -0
- package/dist/actions/paletteModel.js +40 -0
- package/dist/actions/paletteModel.test.d.ts +1 -0
- package/dist/actions/paletteModel.test.js +33 -0
- package/dist/actions/registry.d.ts +10 -0
- package/dist/actions/registry.js +36 -0
- package/dist/actions/registry.test.d.ts +1 -0
- package/dist/actions/registry.test.js +49 -0
- package/dist/actions/selection.svelte.d.ts +8 -0
- package/dist/actions/selection.svelte.js +44 -0
- package/dist/actions/selection.test.d.ts +1 -0
- package/dist/actions/selection.test.js +51 -0
- package/dist/actions/shardContext.test.d.ts +1 -0
- package/dist/actions/shardContext.test.js +41 -0
- package/dist/actions/shellActions.test.d.ts +1 -0
- package/dist/actions/shellActions.test.js +22 -0
- package/dist/actions/shortcuts.d.ts +5 -0
- package/dist/actions/shortcuts.js +87 -0
- package/dist/actions/shortcuts.test.d.ts +1 -0
- package/dist/actions/shortcuts.test.js +49 -0
- package/dist/actions/state.svelte.d.ts +16 -0
- package/dist/actions/state.svelte.js +76 -0
- package/dist/actions/state.test.d.ts +1 -0
- package/dist/actions/state.test.js +40 -0
- package/dist/actions/syncMountedViewIds.test.d.ts +1 -0
- package/dist/actions/syncMountedViewIds.test.js +97 -0
- package/dist/actions/types.d.ts +56 -0
- package/dist/actions/types.js +7 -0
- package/dist/api.d.ts +2 -2
- package/dist/api.js +1 -1
- package/dist/apps/lifecycle.js +13 -3
- package/dist/createShell.js +4 -1
- package/dist/host.js +6 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/layout/LayoutRenderer.browser.test.js +78 -0
- package/dist/layout/LayoutRenderer.svelte +1 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-6-fixed-slots-freezes-the-handle-adjacent-to-a-fixed-pane--dblclick-does-not-collapse-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-1.png +0 -0
- package/dist/layout/inspection.d.ts +11 -1
- package/dist/layout/inspection.js +13 -1
- package/dist/layout/ops-locate.test.d.ts +1 -0
- package/dist/layout/ops-locate.test.js +103 -0
- package/dist/layout/ops.d.ts +8 -0
- package/dist/layout/ops.js +27 -0
- package/dist/layout/slotHostPool.svelte.js +24 -0
- package/dist/layout/slotHostPool.test.js +14 -0
- package/dist/layout/types.d.ts +15 -0
- package/dist/overlays/FloatFrame.svelte +23 -11
- package/dist/overlays/ModalFrame.svelte +9 -1
- package/dist/overlays/ModalFrame.svelte.d.ts +1 -0
- package/dist/overlays/__test__/DummyFrame.svelte +6 -0
- package/dist/overlays/__test__/DummyFrame.svelte.d.ts +6 -0
- package/dist/overlays/float.d.ts +6 -0
- package/dist/overlays/float.js +24 -9
- package/dist/overlays/float.test.js +175 -0
- package/dist/overlays/floatDismiss.d.ts +8 -0
- package/dist/overlays/floatDismiss.js +68 -0
- package/dist/overlays/modal.js +5 -1
- package/dist/overlays/modal.test.d.ts +1 -0
- package/dist/overlays/modal.test.js +55 -0
- package/dist/overlays/popup.d.ts +2 -0
- package/dist/overlays/popup.js +24 -4
- package/dist/overlays/popup.test.d.ts +1 -0
- package/dist/overlays/popup.test.js +95 -0
- package/dist/overlays/types.d.ts +17 -1
- package/dist/primitives/Button.svelte +144 -0
- package/dist/primitives/Button.svelte.d.ts +18 -0
- package/dist/primitives/ResizableSplitter.svelte +38 -3
- package/dist/primitives/ResizableSplitter.svelte.d.ts +7 -0
- package/dist/primitives/icon-context.d.ts +15 -0
- package/dist/primitives/icon-context.js +29 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +50 -0
- package/dist/shards/activate.svelte.js +14 -0
- package/dist/shards/types.d.ts +19 -0
- package/dist/shards/types.js +5 -4
- package/dist/shell-shard/locateSlot.test.d.ts +1 -0
- package/dist/shell-shard/locateSlot.test.js +101 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +7 -0
- package/dist/shell-shard/shellShard.svelte.js +34 -1
- package/dist/shellRuntime.svelte.d.ts +19 -0
- package/dist/shellRuntime.svelte.js +30 -0
- package/dist/tokens.css +11 -1
- package/dist/verbs/types.d.ts +9 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/apps/terminal/manifest.d.ts +0 -8
- package/dist/apps/terminal/manifest.js +0 -14
- package/dist/apps/terminal/terminal-app.d.ts +0 -7
- package/dist/apps/terminal/terminal-app.js +0 -14
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { setActiveApp, setFocusedViewId, setMountedViewIds, setUserBindings, getLiveDispatcherState, __resetDispatcherStateForTest, } from './state.svelte';
|
|
3
|
+
import { __resetSelectionForTest, makeSelectionApi } from './selection.svelte';
|
|
4
|
+
describe('live dispatcher state', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
__resetDispatcherStateForTest();
|
|
7
|
+
__resetSelectionForTest();
|
|
8
|
+
});
|
|
9
|
+
it('starts in home state with empty sets', () => {
|
|
10
|
+
const s = getLiveDispatcherState();
|
|
11
|
+
expect(s.activeAppId).toBeNull();
|
|
12
|
+
expect(s.mountedViewIds.size).toBe(0);
|
|
13
|
+
expect(s.focusedViewId).toBeNull();
|
|
14
|
+
expect(s.selection).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
it('setActiveApp updates activeAppId and requiredShards', () => {
|
|
17
|
+
setActiveApp('app.a', new Set(['shard.x']));
|
|
18
|
+
const s = getLiveDispatcherState();
|
|
19
|
+
expect(s.activeAppId).toBe('app.a');
|
|
20
|
+
expect(s.activeAppRequiredShards.has('shard.x')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('setFocusedViewId updates focus', () => {
|
|
23
|
+
setFocusedViewId('editor');
|
|
24
|
+
expect(getLiveDispatcherState().focusedViewId).toBe('editor');
|
|
25
|
+
});
|
|
26
|
+
it('setMountedViewIds updates mounted', () => {
|
|
27
|
+
setMountedViewIds(new Set(['a', 'b']));
|
|
28
|
+
expect(getLiveDispatcherState().mountedViewIds.has('a')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('setUserBindings updates bindings', () => {
|
|
31
|
+
setUserBindings({ 'shard.x.save': 'Ctrl+Alt+S' });
|
|
32
|
+
expect(getLiveDispatcherState().bindings['shard.x.save']).toBe('Ctrl+Alt+S');
|
|
33
|
+
});
|
|
34
|
+
it('reads selection reactively', () => {
|
|
35
|
+
var _a;
|
|
36
|
+
const api = makeSelectionApi('shard.a');
|
|
37
|
+
api.set({ type: 'orb', ref: 42 });
|
|
38
|
+
expect((_a = getLiveDispatcherState().selection) === null || _a === void 0 ? void 0 : _a.type).toBe('orb');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { resetFramework } from '../__test__/reset';
|
|
3
|
+
import { makeApp, makeAppManifest, makeTabEntry, makeTabsNode, makeTree, makeSlotNode, makeSplitNode } from '../__test__/fixtures';
|
|
4
|
+
import { registerApp } from '../apps/registry.svelte';
|
|
5
|
+
import { launchApp } from '../apps/lifecycle';
|
|
6
|
+
import { syncMountedViewIdsFromLayout, getLiveDispatcherState } from './state.svelte';
|
|
7
|
+
import { bindFloatStore } from '../overlays/float';
|
|
8
|
+
import { layoutStore } from '../layout/store.svelte';
|
|
9
|
+
import { popoutView } from '../layout/inspection';
|
|
10
|
+
describe('syncMountedViewIdsFromLayout', () => {
|
|
11
|
+
beforeEach(resetFramework);
|
|
12
|
+
it('collects viewIds from the docked tree after an app launches', async () => {
|
|
13
|
+
registerApp(makeApp({
|
|
14
|
+
manifest: makeAppManifest({ id: 'sync-app-docked' }),
|
|
15
|
+
initialLayout: [
|
|
16
|
+
{
|
|
17
|
+
name: 'default',
|
|
18
|
+
tree: makeTree(makeTabsNode([
|
|
19
|
+
makeTabEntry({ slotId: 's1', viewId: 'view:a' }),
|
|
20
|
+
makeTabEntry({ slotId: 's2', viewId: 'view:b' }),
|
|
21
|
+
])),
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
}));
|
|
25
|
+
await launchApp('sync-app-docked');
|
|
26
|
+
syncMountedViewIdsFromLayout();
|
|
27
|
+
const mounted = getLiveDispatcherState().mountedViewIds;
|
|
28
|
+
expect(mounted.has('view:a')).toBe(true);
|
|
29
|
+
expect(mounted.has('view:b')).toBe(true);
|
|
30
|
+
expect(mounted.size).toBe(2);
|
|
31
|
+
});
|
|
32
|
+
it('collects viewIds from splits and bare slot leaves', async () => {
|
|
33
|
+
registerApp(makeApp({
|
|
34
|
+
manifest: makeAppManifest({ id: 'sync-app-split' }),
|
|
35
|
+
initialLayout: [
|
|
36
|
+
{
|
|
37
|
+
name: 'default',
|
|
38
|
+
tree: makeTree(makeSplitNode([
|
|
39
|
+
makeSlotNode('leaf-s', 'view:leaf'),
|
|
40
|
+
makeTabsNode([makeTabEntry({ slotId: 'tab-s', viewId: 'view:tabbed' })]),
|
|
41
|
+
])),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}));
|
|
45
|
+
await launchApp('sync-app-split');
|
|
46
|
+
syncMountedViewIdsFromLayout();
|
|
47
|
+
const mounted = getLiveDispatcherState().mountedViewIds;
|
|
48
|
+
expect(mounted.has('view:leaf')).toBe(true);
|
|
49
|
+
expect(mounted.has('view:tabbed')).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('includes viewIds inside floats', async () => {
|
|
52
|
+
registerApp(makeApp({
|
|
53
|
+
manifest: makeAppManifest({ id: 'sync-app-float' }),
|
|
54
|
+
initialLayout: [
|
|
55
|
+
{
|
|
56
|
+
name: 'default',
|
|
57
|
+
tree: makeTree(makeTabsNode([makeTabEntry({ slotId: 'anchor', viewId: 'view:anchor' })])),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}));
|
|
61
|
+
await launchApp('sync-app-float');
|
|
62
|
+
// Bind the float manager to the live tree (Shell.svelte does this at boot).
|
|
63
|
+
bindFloatStore(layoutStore.floats, () => ({ w: 1024, h: 768 }));
|
|
64
|
+
// Popout the anchor → creates a float with view:anchor inside.
|
|
65
|
+
popoutView('anchor');
|
|
66
|
+
syncMountedViewIdsFromLayout();
|
|
67
|
+
const mounted = getLiveDispatcherState().mountedViewIds;
|
|
68
|
+
// Anchor was removed from docked and re-added inside a float.
|
|
69
|
+
expect(mounted.has('view:anchor')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('drops viewIds that leave the tree', async () => {
|
|
72
|
+
registerApp(makeApp({
|
|
73
|
+
manifest: makeAppManifest({ id: 'sync-app-remove' }),
|
|
74
|
+
initialLayout: [
|
|
75
|
+
{
|
|
76
|
+
name: 'default',
|
|
77
|
+
tree: makeTree(makeTabsNode([
|
|
78
|
+
makeTabEntry({ slotId: 'keep', viewId: 'view:keep' }),
|
|
79
|
+
makeTabEntry({ slotId: 'drop', viewId: 'view:drop' }),
|
|
80
|
+
])),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
}));
|
|
84
|
+
await launchApp('sync-app-remove');
|
|
85
|
+
syncMountedViewIdsFromLayout();
|
|
86
|
+
expect(getLiveDispatcherState().mountedViewIds.has('view:drop')).toBe(true);
|
|
87
|
+
// Mutate the tree directly — remove the 'drop' tab.
|
|
88
|
+
const tabs = layoutStore.tree.docked.type === 'tabs' ? layoutStore.tree.docked : null;
|
|
89
|
+
if (tabs) {
|
|
90
|
+
tabs.tabs = tabs.tabs.filter((t) => t.slotId !== 'drop');
|
|
91
|
+
}
|
|
92
|
+
syncMountedViewIdsFromLayout();
|
|
93
|
+
const mounted = getLiveDispatcherState().mountedViewIds;
|
|
94
|
+
expect(mounted.has('view:keep')).toBe(true);
|
|
95
|
+
expect(mounted.has('view:drop')).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type AtomicScope = 'home' | 'app' | `view:${string}` | `focus:${string}` | {
|
|
2
|
+
element: string;
|
|
3
|
+
};
|
|
4
|
+
export type ActionScope = AtomicScope | AtomicScope[];
|
|
5
|
+
export interface Action {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
scope: ActionScope;
|
|
9
|
+
contextItem?: boolean;
|
|
10
|
+
paletteItem?: boolean;
|
|
11
|
+
defaultShortcut?: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
group?: string;
|
|
14
|
+
allowInInputs?: boolean;
|
|
15
|
+
run(ctx: ActionDispatchContext): void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface Selection {
|
|
18
|
+
type: string;
|
|
19
|
+
ref: unknown;
|
|
20
|
+
ownerShardId: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ActionDispatchContext {
|
|
23
|
+
action: {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
};
|
|
27
|
+
appId: string | null;
|
|
28
|
+
viewId?: string;
|
|
29
|
+
selection?: Selection;
|
|
30
|
+
invokedVia: 'keyboard' | 'context-menu' | 'palette' | 'programmatic';
|
|
31
|
+
dispatch(actionId: string): void;
|
|
32
|
+
}
|
|
33
|
+
export interface SelectionApi {
|
|
34
|
+
get(): Selection | null;
|
|
35
|
+
set(sel: {
|
|
36
|
+
type: string;
|
|
37
|
+
ref: unknown;
|
|
38
|
+
}): void;
|
|
39
|
+
clear(): void;
|
|
40
|
+
}
|
|
41
|
+
export interface ActionsApi {
|
|
42
|
+
register(action: Action): () => void;
|
|
43
|
+
selection: SelectionApi;
|
|
44
|
+
openContextMenu(opts: {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
}): void;
|
|
48
|
+
openPalette(opts?: {
|
|
49
|
+
prefill?: string;
|
|
50
|
+
}): void;
|
|
51
|
+
}
|
|
52
|
+
export interface ResolvedAction {
|
|
53
|
+
action: Action;
|
|
54
|
+
ownerShardId: string;
|
|
55
|
+
effectiveShortcut: string | null;
|
|
56
|
+
}
|
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { shell } from './shellRuntime.svelte';
|
|
2
2
|
export type { Shell } from './shellRuntime.svelte';
|
|
3
3
|
export type { Shard, ShardManifest, SourceShard, SourceShardManifest, ShardContext, ViewDeclaration, ViewFactory, ViewHandle, MountContext, } from './shards/types';
|
|
4
|
-
export type { LayoutNode, SplitNode, TabsNode, SlotNode, TabEntry, SplitDirection, SizeMode, LayoutTree, FloatEntry, LayoutPreset, CanonicalPreset, } from './layout/types';
|
|
4
|
+
export type { LayoutNode, SplitNode, TabsNode, SlotNode, TabEntry, SplitDirection, SizeMode, LayoutTree, FloatEntry, LayoutPreset, CanonicalPreset, TreeRootRef as SlotLocation, } from './layout/types';
|
|
5
5
|
export type { FloatManager, FloatOptions } from './overlays/float';
|
|
6
6
|
export type { ModalManager } from './overlays/modal';
|
|
7
7
|
export type { PopupManager } from './overlays/popup';
|
|
@@ -15,7 +15,7 @@ export type { EnvState } from './env/types';
|
|
|
15
15
|
export type { App, AppManifest, SourceApp, SourceAppManifest, AppContext, } from './apps/types';
|
|
16
16
|
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
17
17
|
export { launchApp, returnToHome, unregisterApp } from './apps/lifecycle';
|
|
18
|
-
export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
|
|
18
|
+
export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, popoutView, dockFloat, locateSlot, } from './layout/inspection';
|
|
19
19
|
export type { DocumentHandle, DocumentHandleOptions, DocumentFormat, DocumentMeta, DocumentChange, AutosaveController, } from './documents/types';
|
|
20
20
|
export { PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ, PERMISSION_DOCUMENTS_WRITE, } from './documents/types';
|
|
21
21
|
export type { BrowseCapability } from './documents/browse';
|
package/dist/api.js
CHANGED
|
@@ -28,7 +28,7 @@ export { PERMISSION_STATE_MANAGE } from './state/types';
|
|
|
28
28
|
export { listRegisteredApps, getActiveApp } from './apps/registry.svelte';
|
|
29
29
|
export { launchApp, returnToHome, unregisterApp } from './apps/lifecycle';
|
|
30
30
|
// Layout inspection / mutation for advanced shards (diagnostic, etc.).
|
|
31
|
-
export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, } from './layout/inspection';
|
|
31
|
+
export { inspectActiveLayout, spliceIntoActiveLayout, dockIntoActiveLayout, focusTab, focusView, collapseChild, expandChild, closeTab, popoutView, dockFloat, locateSlot, } from './layout/inspection';
|
|
32
32
|
export { PERMISSION_DOCUMENTS_BROWSE, PERMISSION_DOCUMENTS_READ, PERMISSION_DOCUMENTS_WRITE, } from './documents/types';
|
|
33
33
|
export { PERMISSION_SYNC_PEER, PERMISSION_SYNC_POLICY } from './documents/sync-types';
|
|
34
34
|
export { CONFLICT_RENDERER_POINT, ConflictPermissionError, ConflictSessionOrphanedError, } from './conflicts/api';
|
package/dist/apps/lifecycle.js
CHANGED
|
@@ -17,6 +17,9 @@ import { attachApp, acquireAppSlotHolds, detachApp, switchToApp, switchToHome, }
|
|
|
17
17
|
import { activeApp, getRegisteredApp, registeredApps } from './registry.svelte';
|
|
18
18
|
import { createZoneManager } from '../state/manage';
|
|
19
19
|
import { PERMISSION_STATE_MANAGE } from '../state/types';
|
|
20
|
+
import { setActiveApp, setUserBindings } from '../actions/state.svelte';
|
|
21
|
+
import { clearSelectionUnconditional } from '../actions/selection.svelte';
|
|
22
|
+
import { loadUserBindings } from '../actions/bindings-store';
|
|
20
23
|
// ---------- last-active-app user zone ------------------------------------
|
|
21
24
|
/**
|
|
22
25
|
* Framework-reserved user-zone slot storing which app to boot into on
|
|
@@ -69,7 +72,7 @@ function getOrCreateAppContext(appId) {
|
|
|
69
72
|
* @throws If the app is not registered or a required shard is not registered.
|
|
70
73
|
*/
|
|
71
74
|
export async function launchApp(id) {
|
|
72
|
-
var _a, _b, _c, _d, _e;
|
|
75
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
73
76
|
const app = getRegisteredApp(id);
|
|
74
77
|
if (!app) {
|
|
75
78
|
throw new Error(`Cannot launch app "${id}": not registered`);
|
|
@@ -93,6 +96,8 @@ export async function launchApp(id) {
|
|
|
93
96
|
switchToApp();
|
|
94
97
|
void ((_c = app.onAppReady) === null || _c === void 0 ? void 0 : _c.call(app, getOrCreateAppContext(id)));
|
|
95
98
|
writeLastApp(id);
|
|
99
|
+
setActiveApp(id, new Set((_d = app.manifest.requiredShards) !== null && _d !== void 0 ? _d : []));
|
|
100
|
+
void loadUserBindings(id).then(setUserBindings);
|
|
96
101
|
return;
|
|
97
102
|
}
|
|
98
103
|
// Validate required shards are registered before attaching anything,
|
|
@@ -123,10 +128,12 @@ export async function launchApp(id) {
|
|
|
123
128
|
// refcount holds on the app's slots now (pool's factory lookup
|
|
124
129
|
// happens in a microtask from this call).
|
|
125
130
|
acquireAppSlotHolds();
|
|
126
|
-
void ((
|
|
131
|
+
void ((_e = app.activate) === null || _e === void 0 ? void 0 : _e.call(app, getOrCreateAppContext(id)));
|
|
127
132
|
activeApp.id = id;
|
|
133
|
+
setActiveApp(id, new Set((_f = app.manifest.requiredShards) !== null && _f !== void 0 ? _f : []));
|
|
134
|
+
void loadUserBindings(id).then(setUserBindings);
|
|
128
135
|
switchToApp();
|
|
129
|
-
void ((
|
|
136
|
+
void ((_g = app.onAppReady) === null || _g === void 0 ? void 0 : _g.call(app, getOrCreateAppContext(id)));
|
|
130
137
|
writeLastApp(id);
|
|
131
138
|
}
|
|
132
139
|
// ---------- unload --------------------------------------------------------
|
|
@@ -167,6 +174,9 @@ export function unloadApp(id) {
|
|
|
167
174
|
deactivateShard(shardId);
|
|
168
175
|
}
|
|
169
176
|
activeApp.id = null;
|
|
177
|
+
setActiveApp(null, new Set());
|
|
178
|
+
clearSelectionUnconditional();
|
|
179
|
+
void loadUserBindings('sh3.home').then(setUserBindings);
|
|
170
180
|
appContexts.delete(id);
|
|
171
181
|
}
|
|
172
182
|
// ---------- unregister -------------------------------------------------------
|
package/dist/createShell.js
CHANGED
|
@@ -17,6 +17,7 @@ import { initFromBoot } from './auth/index';
|
|
|
17
17
|
import SignInWall from './auth/SignInWall.svelte';
|
|
18
18
|
import { loadBundleModule } from './registry/loader';
|
|
19
19
|
import { registerLoadedBundle } from './registry/register';
|
|
20
|
+
import { attachGlobalListeners } from './actions/listeners';
|
|
20
21
|
export async function createShell(config) {
|
|
21
22
|
var _a, _b, _c, _d, _e;
|
|
22
23
|
const sUrl = (_a = config === null || config === void 0 ? void 0 : config.serverUrl) !== null && _a !== void 0 ? _a : '';
|
|
@@ -110,7 +111,9 @@ export async function createShell(config) {
|
|
|
110
111
|
if (config === null || config === void 0 ? void 0 : config.excludeShards)
|
|
111
112
|
bootstrapConfig.excludeShards = config.excludeShards;
|
|
112
113
|
await bootstrap(bootstrapConfig);
|
|
113
|
-
// 8.
|
|
114
|
+
// 8. Attach document-level keyboard / focus listeners
|
|
115
|
+
attachGlobalListeners();
|
|
116
|
+
// 9. Mount the shell
|
|
114
117
|
mount(Shell, { target });
|
|
115
118
|
}
|
|
116
119
|
/**
|
package/dist/host.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* imports from `host.ts`.
|
|
17
17
|
*/
|
|
18
18
|
import { registerShard as registerShardInternal, activateShard, registeredShards, } from './shards/activate.svelte';
|
|
19
|
+
import { addAutostartShard } from './actions/state.svelte';
|
|
19
20
|
import { registerApp, registeredApps } from './apps/registry.svelte';
|
|
20
21
|
import { launchApp, readLastApp } from './apps/lifecycle';
|
|
21
22
|
import { sh3coreShard } from './sh3core-shard/sh3coreShard.svelte';
|
|
@@ -27,7 +28,6 @@ import { setLocalOwner } from './auth/index';
|
|
|
27
28
|
import { storeApp } from './app/store/storeApp';
|
|
28
29
|
import { adminShard } from './app/admin/adminShard.svelte';
|
|
29
30
|
import { adminApp } from './app/admin/adminApp';
|
|
30
|
-
import { terminalApp } from './apps/terminal/terminal-app';
|
|
31
31
|
import { runShellRenameMigration, } from './migrations/shell-rename';
|
|
32
32
|
export { __setBackend };
|
|
33
33
|
export { setLocalOwner };
|
|
@@ -65,15 +65,18 @@ export async function bootstrap(config) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
// 2. Framework-shipped apps
|
|
68
|
-
const frameworkApps = [storeApp, adminApp
|
|
68
|
+
const frameworkApps = [storeApp, adminApp];
|
|
69
69
|
for (const app of frameworkApps) {
|
|
70
70
|
registerApp(app);
|
|
71
71
|
}
|
|
72
72
|
// 3. Load any packages installed in a previous session from IndexedDB
|
|
73
73
|
await loadInstalledPackages();
|
|
74
|
-
// 4. Activate every self-starting shard
|
|
74
|
+
// 4. Activate every self-starting shard. Track them in the dispatcher's
|
|
75
|
+
// autostartShards set so the `'app'` action scope treats their actions as
|
|
76
|
+
// ambient (active even inside apps that don't list them as required).
|
|
75
77
|
for (const [id, shard] of registeredShards) {
|
|
76
78
|
if (shard.autostart) {
|
|
79
|
+
addAutostartShard(id);
|
|
77
80
|
await activateShard(id);
|
|
78
81
|
}
|
|
79
82
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './api';
|
|
2
2
|
export { default as Shell } from './Shell.svelte';
|
|
3
|
+
export { default as Button } from './primitives/Button.svelte';
|
|
4
|
+
export { provideIcons, getIconSprite, type ButtonVariant } from './primitives/icon-context';
|
|
3
5
|
export type { ArtifactManifest } from './artifact';
|
|
4
6
|
export * from './shell-shard/protocol';
|
package/dist/index.js
CHANGED
|
@@ -11,4 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export * from './api';
|
|
13
13
|
export { default as Shell } from './Shell.svelte';
|
|
14
|
+
export { default as Button } from './primitives/Button.svelte';
|
|
15
|
+
export { provideIcons, getIconSprite } from './primitives/icon-context';
|
|
14
16
|
export * from './shell-shard/protocol';
|
|
@@ -272,3 +272,81 @@ describe('LayoutRenderer browser — E.5 splitter collapse toggle', () => {
|
|
|
272
272
|
expect((_a = root.collapsed) === null || _a === void 0 ? void 0 : _a[0]).toBe(true);
|
|
273
273
|
});
|
|
274
274
|
});
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// E.6 — fixed[] slots: no collapse widget, frozen handles
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
describe('LayoutRenderer browser — E.6 fixed slots', () => {
|
|
279
|
+
beforeEach(() => { cleanupDOM(); resetFramework(); });
|
|
280
|
+
it('hides the collapse widget on a fixed pane but keeps it on panes with a non-fixed neighbor', async () => {
|
|
281
|
+
stubView();
|
|
282
|
+
registerApp(makeApp({
|
|
283
|
+
manifest: makeAppManifest({ id: 'e6a' }),
|
|
284
|
+
initialLayout: [
|
|
285
|
+
{
|
|
286
|
+
name: 'default',
|
|
287
|
+
tree: makeTree(makeSplitNode([
|
|
288
|
+
makeSlotNode('a', 'test:view'),
|
|
289
|
+
makeSlotNode('b', 'test:view'),
|
|
290
|
+
makeSlotNode('c', 'test:view'),
|
|
291
|
+
], { fixed: [true, false, false] })),
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
}));
|
|
295
|
+
await launchApp('e6a');
|
|
296
|
+
renderWithShell(LayoutRenderer, { path: [] });
|
|
297
|
+
await settle(30);
|
|
298
|
+
expect(document.querySelector('[data-testid="collapse-toggle-0"]')).toBeNull();
|
|
299
|
+
expect(document.querySelector('[data-testid="collapse-toggle-1"]')).not.toBeNull();
|
|
300
|
+
expect(document.querySelector('[data-testid="collapse-toggle-2"]')).not.toBeNull();
|
|
301
|
+
});
|
|
302
|
+
it('freezes the handle adjacent to a fixed pane: dblclick does not collapse', async () => {
|
|
303
|
+
var _a, _b, _c, _d;
|
|
304
|
+
stubView();
|
|
305
|
+
registerApp(makeApp({
|
|
306
|
+
manifest: makeAppManifest({ id: 'e6b' }),
|
|
307
|
+
initialLayout: [
|
|
308
|
+
{
|
|
309
|
+
name: 'default',
|
|
310
|
+
tree: makeTree(makeSplitNode([makeSlotNode('a', 'test:view'), makeSlotNode('b', 'test:view')], { fixed: [true, false] })),
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
}));
|
|
314
|
+
await launchApp('e6b');
|
|
315
|
+
renderWithShell(LayoutRenderer, { path: [] });
|
|
316
|
+
await settle(30);
|
|
317
|
+
const handle = document.querySelector('[data-testid="splitter-handle-0"]');
|
|
318
|
+
expect(handle).not.toBeNull();
|
|
319
|
+
expect(handle.classList.contains('frozen')).toBe(true);
|
|
320
|
+
// pointer-events: none blocks Playwright clicks, so dispatch the event
|
|
321
|
+
// directly to verify the handler itself refuses to toggle.
|
|
322
|
+
handle.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }));
|
|
323
|
+
await settle(50);
|
|
324
|
+
const root = layoutStore.root;
|
|
325
|
+
if ((root === null || root === void 0 ? void 0 : root.type) !== 'split')
|
|
326
|
+
throw new Error('expected split root');
|
|
327
|
+
expect((_b = (_a = root.collapsed) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : false).toBe(false);
|
|
328
|
+
expect((_d = (_c = root.collapsed) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : false).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
it('hides the collapse widget on a middle pane whose neighbors are both fixed', async () => {
|
|
331
|
+
stubView();
|
|
332
|
+
registerApp(makeApp({
|
|
333
|
+
manifest: makeAppManifest({ id: 'e6c' }),
|
|
334
|
+
initialLayout: [
|
|
335
|
+
{
|
|
336
|
+
name: 'default',
|
|
337
|
+
tree: makeTree(makeSplitNode([
|
|
338
|
+
makeSlotNode('a', 'test:view'),
|
|
339
|
+
makeSlotNode('b', 'test:view'),
|
|
340
|
+
makeSlotNode('c', 'test:view'),
|
|
341
|
+
], { fixed: [true, false, true] })),
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
}));
|
|
345
|
+
await launchApp('e6c');
|
|
346
|
+
renderWithShell(LayoutRenderer, { path: [] });
|
|
347
|
+
await settle(30);
|
|
348
|
+
expect(document.querySelector('[data-testid="collapse-toggle-0"]')).toBeNull();
|
|
349
|
+
expect(document.querySelector('[data-testid="collapse-toggle-1"]')).toBeNull();
|
|
350
|
+
expect(document.querySelector('[data-testid="collapse-toggle-2"]')).toBeNull();
|
|
351
|
+
});
|
|
352
|
+
});
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TabEntry, LayoutTree } from './types';
|
|
1
|
+
import type { TabEntry, LayoutTree, TreeRootRef } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Read-only snapshot of the currently-rendered layout tree. The return
|
|
4
4
|
* value is the live object — callers MUST NOT mutate it directly;
|
|
@@ -87,3 +87,13 @@ export declare function dockFloat(floatId: string): boolean;
|
|
|
87
87
|
* was empty or otherwise un-dockable.
|
|
88
88
|
*/
|
|
89
89
|
export declare function dockIntoActiveLayout(entry: TabEntry): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Find which root a slot currently lives under in the active layout.
|
|
92
|
+
* Returns `{ kind: 'docked' }` when the slot is anywhere in the docked
|
|
93
|
+
* tree, `{ kind: 'float', floatId }` when it lives inside a float, or
|
|
94
|
+
* `null` when the slot is not present (stale id, post-unmount, or
|
|
95
|
+
* held-but-not-active app tree). Thin wrapper over the pure
|
|
96
|
+
* `locateSlotIn(tree, slotId)` from `ops.ts` — use this when you only
|
|
97
|
+
* have a slotId in hand; use `locateSlotIn` if you already hold the tree.
|
|
98
|
+
*/
|
|
99
|
+
export declare function locateSlot(slotId: string): TreeRootRef | null;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* rendered.
|
|
14
14
|
*/
|
|
15
15
|
import { activeLayout, getActiveRoot } from './store.svelte';
|
|
16
|
-
import { nodeAtPath, findTabBySlotId, removeTabBySlotId, cleanupTree, splitNodeAtPath, } from './ops';
|
|
16
|
+
import { nodeAtPath, findTabBySlotId, removeTabBySlotId, cleanupTree, splitNodeAtPath, locateSlotIn, } from './ops';
|
|
17
17
|
import { getSlotHandle } from './slotHostPool.svelte';
|
|
18
18
|
import { floatManager } from '../overlays/float';
|
|
19
19
|
/**
|
|
@@ -311,3 +311,15 @@ export function dockIntoActiveLayout(entry) {
|
|
|
311
311
|
splitNodeAtPath(root, slotPath, entry, 'right');
|
|
312
312
|
return true;
|
|
313
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Find which root a slot currently lives under in the active layout.
|
|
316
|
+
* Returns `{ kind: 'docked' }` when the slot is anywhere in the docked
|
|
317
|
+
* tree, `{ kind: 'float', floatId }` when it lives inside a float, or
|
|
318
|
+
* `null` when the slot is not present (stale id, post-unmount, or
|
|
319
|
+
* held-but-not-active app tree). Thin wrapper over the pure
|
|
320
|
+
* `locateSlotIn(tree, slotId)` from `ops.ts` — use this when you only
|
|
321
|
+
* have a slotId in hand; use `locateSlotIn` if you already hold the tree.
|
|
322
|
+
*/
|
|
323
|
+
export function locateSlot(slotId) {
|
|
324
|
+
return locateSlotIn(activeLayout(), slotId);
|
|
325
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { locateSlotIn } from './ops';
|
|
3
|
+
describe('locateSlotIn', () => {
|
|
4
|
+
it('finds a tab-entry slot in the docked tree', () => {
|
|
5
|
+
const tree = {
|
|
6
|
+
docked: {
|
|
7
|
+
type: 'tabs',
|
|
8
|
+
tabs: [{ slotId: 'dock-a', viewId: 'v', label: 'A' }],
|
|
9
|
+
activeTab: 0,
|
|
10
|
+
},
|
|
11
|
+
floats: [],
|
|
12
|
+
};
|
|
13
|
+
expect(locateSlotIn(tree, 'dock-a')).toEqual({ kind: 'docked' });
|
|
14
|
+
});
|
|
15
|
+
it('finds a bare slot leaf in the docked tree', () => {
|
|
16
|
+
const tree = {
|
|
17
|
+
docked: { type: 'slot', slotId: 'dock-leaf', viewId: 'v' },
|
|
18
|
+
floats: [],
|
|
19
|
+
};
|
|
20
|
+
expect(locateSlotIn(tree, 'dock-leaf')).toEqual({ kind: 'docked' });
|
|
21
|
+
});
|
|
22
|
+
it('finds a slot nested inside a split', () => {
|
|
23
|
+
const tree = {
|
|
24
|
+
docked: {
|
|
25
|
+
type: 'split',
|
|
26
|
+
direction: 'horizontal',
|
|
27
|
+
sizes: [0.5, 0.5],
|
|
28
|
+
children: [
|
|
29
|
+
{ type: 'slot', slotId: 'left', viewId: 'v' },
|
|
30
|
+
{
|
|
31
|
+
type: 'tabs',
|
|
32
|
+
tabs: [{ slotId: 'right', viewId: 'v', label: 'R' }],
|
|
33
|
+
activeTab: 0,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
floats: [],
|
|
38
|
+
};
|
|
39
|
+
expect(locateSlotIn(tree, 'left')).toEqual({ kind: 'docked' });
|
|
40
|
+
expect(locateSlotIn(tree, 'right')).toEqual({ kind: 'docked' });
|
|
41
|
+
});
|
|
42
|
+
it('finds a tab-entry slot inside a float', () => {
|
|
43
|
+
const tree = {
|
|
44
|
+
docked: { type: 'tabs', tabs: [], activeTab: 0 },
|
|
45
|
+
floats: [
|
|
46
|
+
{
|
|
47
|
+
id: 'float-1',
|
|
48
|
+
content: {
|
|
49
|
+
type: 'tabs',
|
|
50
|
+
tabs: [{ slotId: 'float-a', viewId: 'v', label: 'A' }],
|
|
51
|
+
activeTab: 0,
|
|
52
|
+
},
|
|
53
|
+
position: { x: 0, y: 0 },
|
|
54
|
+
size: { w: 600, h: 400 },
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
expect(locateSlotIn(tree, 'float-a')).toEqual({ kind: 'float', floatId: 'float-1' });
|
|
59
|
+
});
|
|
60
|
+
it('finds a bare slot leaf inside a float', () => {
|
|
61
|
+
const tree = {
|
|
62
|
+
docked: { type: 'tabs', tabs: [], activeTab: 0 },
|
|
63
|
+
floats: [
|
|
64
|
+
{
|
|
65
|
+
id: 'float-2',
|
|
66
|
+
content: { type: 'slot', slotId: 'float-leaf', viewId: 'v' },
|
|
67
|
+
position: { x: 0, y: 0 },
|
|
68
|
+
size: { w: 600, h: 400 },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
expect(locateSlotIn(tree, 'float-leaf')).toEqual({ kind: 'float', floatId: 'float-2' });
|
|
73
|
+
});
|
|
74
|
+
it('returns null for an absent slot', () => {
|
|
75
|
+
const tree = {
|
|
76
|
+
docked: { type: 'tabs', tabs: [], activeTab: 0 },
|
|
77
|
+
floats: [],
|
|
78
|
+
};
|
|
79
|
+
expect(locateSlotIn(tree, 'nope')).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
it('prefers docked when an id is (illegally) present in both', () => {
|
|
82
|
+
const tree = {
|
|
83
|
+
docked: {
|
|
84
|
+
type: 'tabs',
|
|
85
|
+
tabs: [{ slotId: 'dup', viewId: 'v', label: 'D' }],
|
|
86
|
+
activeTab: 0,
|
|
87
|
+
},
|
|
88
|
+
floats: [
|
|
89
|
+
{
|
|
90
|
+
id: 'float-3',
|
|
91
|
+
content: {
|
|
92
|
+
type: 'tabs',
|
|
93
|
+
tabs: [{ slotId: 'dup', viewId: 'v', label: 'D' }],
|
|
94
|
+
activeTab: 0,
|
|
95
|
+
},
|
|
96
|
+
position: { x: 0, y: 0 },
|
|
97
|
+
size: { w: 600, h: 400 },
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
expect(locateSlotIn(tree, 'dup')).toEqual({ kind: 'docked' });
|
|
102
|
+
});
|
|
103
|
+
});
|
package/dist/layout/ops.d.ts
CHANGED
|
@@ -33,6 +33,14 @@ export interface LocatedTabInTree {
|
|
|
33
33
|
* Returns null if the slot id is not present in any root.
|
|
34
34
|
*/
|
|
35
35
|
export declare function findTabInTree(tree: LayoutTree, slotId: string): LocatedTabInTree | null;
|
|
36
|
+
/**
|
|
37
|
+
* Locate the root a slot currently lives under in the given tree. Handles
|
|
38
|
+
* both tab entries and bare slot leaves, docked first then each float's
|
|
39
|
+
* content. Returns null when the slot is not present anywhere. Pure —
|
|
40
|
+
* takes the tree as input so callers with an in-hand tree (and tests)
|
|
41
|
+
* don't need the layout store.
|
|
42
|
+
*/
|
|
43
|
+
export declare function locateSlotIn(tree: LayoutTree, slotId: string): TreeRootRef | null;
|
|
36
44
|
/**
|
|
37
45
|
* Remove a tab from its current location, returning the removed entry
|
|
38
46
|
* (or null if not found). The tabs group it was in may become empty
|