sh3-core 0.11.4 → 0.11.7
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/BrandSlot.svelte +80 -0
- package/dist/BrandSlot.svelte.d.ts +3 -0
- package/dist/BrandSlot.test.d.ts +1 -0
- package/dist/BrandSlot.test.js +71 -0
- package/dist/Shell.svelte +8 -10
- package/dist/actions/ActionPanel.svelte +143 -0
- package/dist/actions/ActionPanel.svelte.d.ts +13 -0
- package/dist/actions/ActionPanel.test.d.ts +1 -0
- package/dist/actions/ActionPanel.test.js +168 -0
- package/dist/actions/ContextMenu.svelte +17 -85
- package/dist/actions/MenuBar.svelte +57 -0
- package/dist/actions/MenuBar.svelte.d.ts +3 -0
- package/dist/actions/MenuBar.test.d.ts +1 -0
- package/dist/actions/MenuBar.test.js +109 -0
- package/dist/actions/MenuButton.svelte +150 -0
- package/dist/actions/MenuButton.svelte.d.ts +10 -0
- package/dist/actions/MenuButton.test.d.ts +1 -0
- package/dist/actions/MenuButton.test.js +125 -0
- package/dist/actions/contextMenuModel.d.ts +10 -0
- package/dist/actions/contextMenuModel.js +44 -9
- package/dist/actions/contextMenuModel.test.js +28 -1
- package/dist/actions/defaultMenuContainers.d.ts +2 -0
- package/dist/actions/defaultMenuContainers.js +7 -0
- package/dist/actions/defaultMenuContainers.test.d.ts +1 -0
- package/dist/actions/defaultMenuContainers.test.js +23 -0
- package/dist/actions/listeners.d.ts +4 -0
- package/dist/actions/listeners.js +77 -17
- package/dist/actions/listeners.test.js +50 -0
- package/dist/actions/menuBarModel.d.ts +42 -0
- package/dist/actions/menuBarModel.js +110 -0
- package/dist/actions/menuBarModel.test.d.ts +1 -0
- package/dist/actions/menuBarModel.test.js +158 -0
- package/dist/actions/palette-scorer.d.ts +4 -0
- package/dist/actions/palette-scorer.js +5 -0
- package/dist/actions/palette-scorer.test.js +9 -1
- package/dist/actions/paletteModel.d.ts +7 -1
- package/dist/actions/paletteModel.js +26 -1
- package/dist/actions/paletteModel.test.js +43 -0
- package/dist/actions/registry.js +5 -0
- package/dist/actions/registry.test.js +12 -0
- package/dist/actions/types.d.ts +48 -1
- package/dist/actions/types.test.d.ts +1 -0
- package/dist/actions/types.test.js +31 -0
- package/dist/apps/lifecycle.js +8 -1
- package/dist/apps/lifecycle.test.js +211 -1
- package/dist/apps/registry.svelte.d.ts +17 -1
- package/dist/apps/registry.svelte.js +20 -1
- package/dist/apps/types.d.ts +28 -0
- package/dist/assets/icons.svg +5 -0
- package/dist/documents/backends.d.ts +2 -0
- package/dist/documents/backends.js +55 -0
- package/dist/documents/backends.test.d.ts +1 -1
- package/dist/documents/backends.test.js +69 -1
- package/dist/documents/browse.d.ts +18 -0
- package/dist/documents/browse.js +13 -0
- package/dist/documents/browse.test.js +47 -0
- package/dist/documents/handle.js +23 -0
- package/dist/documents/handle.test.js +51 -0
- package/dist/documents/http-backend.d.ts +1 -0
- package/dist/documents/http-backend.js +19 -0
- package/dist/documents/http-backend.test.js +42 -0
- package/dist/documents/types.d.ts +29 -1
- package/dist/documents/types.js +4 -0
- package/dist/documents/types.test.d.ts +1 -0
- package/dist/documents/types.test.js +20 -0
- package/dist/layout/LayoutRenderer.browser.test.js +196 -0
- package/dist/layout/SlotContainer.svelte +13 -8
- package/dist/layout/SlotDropZone.svelte +44 -9
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-7-fixed-slot-drop-protection-still-accepts-a-strip-drop-into-a-fixed-tabs-node-1.png +0 -0
- package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-8-same-strip-reorder-keeps-the-active-pane-populated-after-moving-the-second-tab-to-first-1.png +0 -0
- package/dist/layout/ops.d.ts +10 -0
- package/dist/layout/ops.js +30 -2
- package/dist/layout/ops.test.js +111 -1
- package/dist/layout/slotHostPool.svelte.d.ts +7 -1
- package/dist/layout/slotHostPool.svelte.js +27 -8
- package/dist/layout/store.svelte.d.ts +27 -0
- package/dist/layout/store.svelte.js +63 -0
- package/dist/overlays/ConfirmDialog.svelte +138 -0
- package/dist/overlays/ConfirmDialog.svelte.d.ts +13 -0
- package/dist/overlays/ConfirmDialog.test.d.ts +1 -0
- package/dist/overlays/ConfirmDialog.test.js +123 -0
- package/dist/overlays/FloatFrame.svelte +2 -2
- package/dist/overlays/ToastItem.svelte +3 -3
- package/dist/primitives/base.css +5 -5
- package/dist/sh3core-shard/sh3coreShard.svelte.js +38 -4
- package/dist/shell-shard/shellShard.svelte.js +0 -4
- package/dist/tokens.css +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
|
@@ -47,3 +47,46 @@ describe('buildPaletteCandidates', () => {
|
|
|
47
47
|
expect(out[0].scopeBadge).toBe('view:editor');
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
|
+
describe('buildPaletteCandidates — submenu and filter', () => {
|
|
51
|
+
it('filter.submenuOf: only returns children of the given parent', () => {
|
|
52
|
+
const entries = [
|
|
53
|
+
mkEntry({ id: 'p', scope: 'home', label: 'P', submenu: true }),
|
|
54
|
+
mkEntry({ id: 'p.a', scope: 'home', label: 'A', submenuOf: 'p' }),
|
|
55
|
+
mkEntry({ id: 'p.b', scope: 'home', label: 'B', submenuOf: 'p' }),
|
|
56
|
+
mkEntry({ id: 'q.x', scope: 'home', label: 'X', submenuOf: 'q' }),
|
|
57
|
+
mkEntry({ id: 'r', scope: 'home', label: 'R' }),
|
|
58
|
+
];
|
|
59
|
+
const out = buildPaletteCandidates(entries, mkState(), { filter: { submenuOf: 'p' } });
|
|
60
|
+
expect(out.map((c) => c.id)).toEqual(['p.a', 'p.b']);
|
|
61
|
+
});
|
|
62
|
+
it('returns all active candidates by default (children included; scorer hides them on empty query)', () => {
|
|
63
|
+
const entries = [
|
|
64
|
+
mkEntry({ id: 'p', scope: 'home', label: 'P', submenu: true }),
|
|
65
|
+
mkEntry({ id: 'p.a', scope: 'home', label: 'A', submenuOf: 'p' }),
|
|
66
|
+
];
|
|
67
|
+
const out = buildPaletteCandidates(entries, mkState());
|
|
68
|
+
expect(out.map((c) => c.id).sort()).toEqual(['p', 'p.a']);
|
|
69
|
+
});
|
|
70
|
+
it('hides disabled actions', () => {
|
|
71
|
+
const entries = [
|
|
72
|
+
mkEntry({ id: 'a', scope: 'home', label: 'A', disabled: true }),
|
|
73
|
+
mkEntry({ id: 'b', scope: 'home', label: 'B', disabled: () => true }),
|
|
74
|
+
mkEntry({ id: 'c', scope: 'home', label: 'C' }),
|
|
75
|
+
];
|
|
76
|
+
const out = buildPaletteCandidates(entries, mkState(), {});
|
|
77
|
+
expect(out.map((c) => c.id)).toEqual(['c']);
|
|
78
|
+
});
|
|
79
|
+
it('annotates submenu and submenuOf on candidates', () => {
|
|
80
|
+
const entries = [
|
|
81
|
+
mkEntry({ id: 'p', scope: 'home', label: 'P', submenu: true }),
|
|
82
|
+
mkEntry({ id: 'p.a', scope: 'home', label: 'A', submenuOf: 'p' }),
|
|
83
|
+
];
|
|
84
|
+
const out = buildPaletteCandidates(entries, mkState());
|
|
85
|
+
const parent = out.find((c) => c.id === 'p');
|
|
86
|
+
const child = out.find((c) => c.id === 'p.a');
|
|
87
|
+
expect(parent.submenu).toBe(true);
|
|
88
|
+
expect(parent.submenuOf).toBeUndefined();
|
|
89
|
+
expect(child.submenu).toBe(false);
|
|
90
|
+
expect(child.submenuOf).toBe('p');
|
|
91
|
+
});
|
|
92
|
+
});
|
package/dist/actions/registry.js
CHANGED
|
@@ -9,6 +9,11 @@ import { register as contributionsRegister, list as contributionsList, onChange
|
|
|
9
9
|
export const ACTIONS_POINT_ID = 'sh3.actions';
|
|
10
10
|
const liveIds = new Set();
|
|
11
11
|
export function registerAction(action, ownerShardId) {
|
|
12
|
+
if (typeof action.run !== 'function' && action.submenu !== true) {
|
|
13
|
+
console.warn(`[sh3] Action "${action.id}" registered by "${ownerShardId}" has no \`run\` ` +
|
|
14
|
+
`and is not a submenu parent (\`submenu: true\`). Registration ignored.`);
|
|
15
|
+
return () => { };
|
|
16
|
+
}
|
|
12
17
|
if (liveIds.has(action.id)) {
|
|
13
18
|
console.warn(`[sh3] Duplicate action id "${action.id}" registered by "${ownerShardId}". ` +
|
|
14
19
|
`First registration wins; second registration still stored but shortcut conflicts may occur.`);
|
|
@@ -46,4 +46,16 @@ describe('actions registry', () => {
|
|
|
46
46
|
expect(warn).toHaveBeenCalled();
|
|
47
47
|
warn.mockRestore();
|
|
48
48
|
});
|
|
49
|
+
it('warns and rejects an action with neither run nor submenu', () => {
|
|
50
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
51
|
+
const dispose = registerAction({ id: 'no-op', label: 'No-Op', scope: 'home' }, 'shard.a');
|
|
52
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('"no-op"'));
|
|
53
|
+
expect(listActions()).toHaveLength(0);
|
|
54
|
+
dispose();
|
|
55
|
+
warn.mockRestore();
|
|
56
|
+
});
|
|
57
|
+
it('accepts a submenu parent without run', () => {
|
|
58
|
+
registerAction({ id: 'p', label: 'Parent', scope: 'home', submenu: true }, 'shard.a');
|
|
59
|
+
expect(listActions()).toHaveLength(1);
|
|
60
|
+
});
|
|
49
61
|
});
|
package/dist/actions/types.d.ts
CHANGED
|
@@ -8,11 +8,55 @@ export interface Action {
|
|
|
8
8
|
scope: ActionScope;
|
|
9
9
|
contextItem?: boolean;
|
|
10
10
|
paletteItem?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Optional menu container id. When set and the active app's declared
|
|
13
|
+
* (or canonical fallback) menu list contains this id, the action
|
|
14
|
+
* appears in that container's dropdown. Orphaned values render
|
|
15
|
+
* nowhere in the menu bar; the action remains reachable via
|
|
16
|
+
* palette/hotkey/context menu.
|
|
17
|
+
*/
|
|
18
|
+
menuItem?: string;
|
|
11
19
|
defaultShortcut?: string;
|
|
12
20
|
icon?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional grouping key. Items inside the same surface (menu bar
|
|
23
|
+
* dropdown, context menu, submenu popup) with distinct `group`
|
|
24
|
+
* values are separated by a divider line in `ActionPanel`. This is
|
|
25
|
+
* the only separator mechanism — there is no explicit separator
|
|
26
|
+
* primitive. Items without a `group` form a single default group.
|
|
27
|
+
*/
|
|
13
28
|
group?: string;
|
|
14
29
|
allowInInputs?: boolean;
|
|
15
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Toggle-state indicator. Truthy renders a leading ✓ in MenuBar /
|
|
32
|
+
* ContextMenu rows. Function form is re-evaluated on each derive
|
|
33
|
+
* (no re-registration on flip). Ignored in CommandPalette.
|
|
34
|
+
*/
|
|
35
|
+
checked?: boolean | (() => boolean);
|
|
36
|
+
/**
|
|
37
|
+
* Visible-but-not-dispatchable. Greyed in MenuBar/ContextMenu,
|
|
38
|
+
* skipped by keyboard nav, click is a no-op, shortcut dispatch is
|
|
39
|
+
* blocked. Hidden from CommandPalette in v1 (a future "show
|
|
40
|
+
* disabled" toggle is acknowledged in the spec but out of scope).
|
|
41
|
+
*/
|
|
42
|
+
disabled?: boolean | (() => boolean);
|
|
43
|
+
/**
|
|
44
|
+
* Marks this action as a submenu parent. `run` becomes optional —
|
|
45
|
+
* when omitted, the framework provides default drill-down behavior:
|
|
46
|
+
* MenuBar/ContextMenu render the row as actionless+expanding;
|
|
47
|
+
* CommandPalette opens a sub-palette filtered to children
|
|
48
|
+
* (`submenuOf === this.id`).
|
|
49
|
+
*/
|
|
50
|
+
submenu?: true;
|
|
51
|
+
/**
|
|
52
|
+
* Marks this action as a child of `submenuOf`'s submenu. Children
|
|
53
|
+
* inherit the parent's surface placement — they do NOT repeat
|
|
54
|
+
* `menuItem`, `contextItem`, or `paletteItem`. Children are excluded
|
|
55
|
+
* from their parent's container's flat list and only appear in the
|
|
56
|
+
* parent's submenu popup or via direct match in the palette.
|
|
57
|
+
*/
|
|
58
|
+
submenuOf?: string;
|
|
59
|
+
run?(ctx: ActionDispatchContext): void | Promise<void>;
|
|
16
60
|
}
|
|
17
61
|
export interface Selection {
|
|
18
62
|
type: string;
|
|
@@ -47,6 +91,9 @@ export interface ActionsApi {
|
|
|
47
91
|
}): void;
|
|
48
92
|
openPalette(opts?: {
|
|
49
93
|
prefill?: string;
|
|
94
|
+
filter?: {
|
|
95
|
+
submenuOf?: string;
|
|
96
|
+
};
|
|
50
97
|
}): void;
|
|
51
98
|
}
|
|
52
99
|
export interface ResolvedAction {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expectTypeOf } from 'vitest';
|
|
2
|
+
describe('Action type', () => {
|
|
3
|
+
it('accepts an action without run when submenu: true', () => {
|
|
4
|
+
const a = {
|
|
5
|
+
id: 'p',
|
|
6
|
+
label: 'Parent',
|
|
7
|
+
scope: 'home',
|
|
8
|
+
submenu: true,
|
|
9
|
+
};
|
|
10
|
+
expectTypeOf(a).toMatchTypeOf();
|
|
11
|
+
});
|
|
12
|
+
it('accepts checked / disabled as boolean or function', () => {
|
|
13
|
+
const flat = {
|
|
14
|
+
id: 'a', label: 'A', scope: 'home',
|
|
15
|
+
run: () => { }, checked: true, disabled: false,
|
|
16
|
+
};
|
|
17
|
+
const reactive = {
|
|
18
|
+
id: 'b', label: 'B', scope: 'home',
|
|
19
|
+
run: () => { }, checked: () => true, disabled: () => false,
|
|
20
|
+
};
|
|
21
|
+
expectTypeOf(flat).toMatchTypeOf();
|
|
22
|
+
expectTypeOf(reactive).toMatchTypeOf();
|
|
23
|
+
});
|
|
24
|
+
it('accepts submenuOf as a string id', () => {
|
|
25
|
+
const c = {
|
|
26
|
+
id: 'c', label: 'C', scope: 'home',
|
|
27
|
+
submenuOf: 'p', run: () => { },
|
|
28
|
+
};
|
|
29
|
+
expectTypeOf(c).toMatchTypeOf();
|
|
30
|
+
});
|
|
31
|
+
});
|
package/dist/apps/lifecycle.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { createStateZones } from '../state/zones.svelte';
|
|
15
15
|
import { activateShard, deactivateShard, getShardContext, registeredShards, } from '../shards/activate.svelte';
|
|
16
16
|
import { attachApp, acquireAppSlotHolds, detachApp, switchToApp, switchToHome, } from '../layout/store.svelte';
|
|
17
|
-
import { activeApp, getRegisteredApp, registeredApps } from './registry.svelte';
|
|
17
|
+
import { activeApp, breadcrumbApp, getRegisteredApp, registeredApps } from './registry.svelte';
|
|
18
18
|
import { createZoneManager } from '../state/manage';
|
|
19
19
|
import { PERMISSION_STATE_MANAGE } from '../state/types';
|
|
20
20
|
import { setActiveApp, setUserBindings } from '../actions/state.svelte';
|
|
@@ -96,6 +96,7 @@ export async function launchApp(id) {
|
|
|
96
96
|
switchToApp();
|
|
97
97
|
void ((_c = app.onAppReady) === null || _c === void 0 ? void 0 : _c.call(app, getOrCreateAppContext(id)));
|
|
98
98
|
writeLastApp(id);
|
|
99
|
+
breadcrumbApp.id = id;
|
|
99
100
|
setActiveApp(id, new Set((_d = app.manifest.requiredShards) !== null && _d !== void 0 ? _d : []));
|
|
100
101
|
void loadUserBindings(id).then(setUserBindings);
|
|
101
102
|
return;
|
|
@@ -135,6 +136,7 @@ export async function launchApp(id) {
|
|
|
135
136
|
switchToApp();
|
|
136
137
|
void ((_g = app.onAppReady) === null || _g === void 0 ? void 0 : _g.call(app, getOrCreateAppContext(id)));
|
|
137
138
|
writeLastApp(id);
|
|
139
|
+
breadcrumbApp.id = id;
|
|
138
140
|
}
|
|
139
141
|
// ---------- unload --------------------------------------------------------
|
|
140
142
|
/**
|
|
@@ -226,6 +228,11 @@ export async function returnToHome() {
|
|
|
226
228
|
return false;
|
|
227
229
|
}
|
|
228
230
|
switchToHome();
|
|
231
|
+
// Mirror unregisterApp: clear the dispatcher's active-app pointer so
|
|
232
|
+
// 'app'-scope actions become inactive on home. Without this, any action
|
|
233
|
+
// registered with scope: ['app'] keeps appearing in the palette while
|
|
234
|
+
// the user is on home.
|
|
235
|
+
setActiveApp(null, new Set());
|
|
229
236
|
writeLastApp(null);
|
|
230
237
|
return true;
|
|
231
238
|
}
|
|
@@ -5,7 +5,7 @@ import { launchApp, returnToHome, unregisterApp } from './lifecycle';
|
|
|
5
5
|
import { registerApp } from './registry.svelte';
|
|
6
6
|
import { registerShard } from '../shards/activate.svelte';
|
|
7
7
|
import { presetManager } from '../overlays/presets';
|
|
8
|
-
import { layoutStore } from '../layout/store.svelte';
|
|
8
|
+
import { layoutStore, resetActivePresetToDefault } from '../layout/store.svelte';
|
|
9
9
|
import LayoutRenderer from '../layout/LayoutRenderer.svelte';
|
|
10
10
|
import { renderWithShell } from '../__test__/render';
|
|
11
11
|
import { registerView } from '../shards/registry';
|
|
@@ -307,3 +307,213 @@ describe('installPackage evict-before-register (simulated via registerLoadedBund
|
|
|
307
307
|
expect((_b = registeredShards.get('S')) === null || _b === void 0 ? void 0 : _b.manifest.version).toBe('1.0.1');
|
|
308
308
|
});
|
|
309
309
|
});
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Scenario C.1 — resetActivePresetToDefault rebuilds the active preset
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
describe('resetActivePresetToDefault — scenario C.1 happy path', () => {
|
|
314
|
+
beforeEach(resetFramework);
|
|
315
|
+
it('replaces the active preset tree with a fresh copy of initialLayout', async () => {
|
|
316
|
+
var _a;
|
|
317
|
+
registerApp(makeApp({
|
|
318
|
+
manifest: makeAppManifest({ id: 'reset-1' }),
|
|
319
|
+
initialLayout: [
|
|
320
|
+
{
|
|
321
|
+
name: 'main',
|
|
322
|
+
tree: makeTree(makeTabsNode([
|
|
323
|
+
makeTabEntry({ slotId: 'a', label: 'A' }),
|
|
324
|
+
makeTabEntry({ slotId: 'b', label: 'B' }),
|
|
325
|
+
])),
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
}));
|
|
329
|
+
await launchApp('reset-1');
|
|
330
|
+
// Mutate the live tree: change activeTab and add a float.
|
|
331
|
+
const root = layoutStore.root;
|
|
332
|
+
expect(root === null || root === void 0 ? void 0 : root.type).toBe('tabs');
|
|
333
|
+
if ((root === null || root === void 0 ? void 0 : root.type) === 'tabs')
|
|
334
|
+
root.activeTab = 1;
|
|
335
|
+
layoutStore.tree.floats.push({
|
|
336
|
+
id: 'float-test',
|
|
337
|
+
content: makeSlotNode('floated'),
|
|
338
|
+
position: { x: 10, y: 10 },
|
|
339
|
+
size: { w: 100, h: 100 },
|
|
340
|
+
});
|
|
341
|
+
expect(layoutStore.tree.floats.length).toBe(1);
|
|
342
|
+
resetActivePresetToDefault();
|
|
343
|
+
const resetRoot = layoutStore.root;
|
|
344
|
+
expect(resetRoot === null || resetRoot === void 0 ? void 0 : resetRoot.type).toBe('tabs');
|
|
345
|
+
if ((resetRoot === null || resetRoot === void 0 ? void 0 : resetRoot.type) === 'tabs') {
|
|
346
|
+
expect((_a = resetRoot.activeTab) !== null && _a !== void 0 ? _a : 0).toBe(0);
|
|
347
|
+
expect(resetRoot.tabs.map((t) => t.slotId)).toEqual(['a', 'b']);
|
|
348
|
+
}
|
|
349
|
+
expect(layoutStore.tree.floats.length).toBe(0);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Scenario C.2 — reset falls back when active preset name no longer exists
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
describe('resetActivePresetToDefault — scenario C.2 missing-preset fallback', () => {
|
|
356
|
+
beforeEach(resetFramework);
|
|
357
|
+
it('falls back to canonical[0] and updates activePreset when the stored name is gone', async () => {
|
|
358
|
+
// Launch with two presets, switch to the second, then simulate the app
|
|
359
|
+
// dropping that preset by re-registering it with a different layout.
|
|
360
|
+
registerApp(makeApp({
|
|
361
|
+
manifest: makeAppManifest({ id: 'reset-2' }),
|
|
362
|
+
initialLayout: [
|
|
363
|
+
{ name: 'first', tree: makeTree(makeSlotNode('x')) },
|
|
364
|
+
{ name: 'second', tree: makeTree(makeSlotNode('y')) },
|
|
365
|
+
],
|
|
366
|
+
}));
|
|
367
|
+
await launchApp('reset-2');
|
|
368
|
+
presetManager.switch('second');
|
|
369
|
+
expect(presetManager.active()).toBe('second');
|
|
370
|
+
// Replace the registry entry with a version that no longer declares
|
|
371
|
+
// 'second'. We mutate registeredApps directly because the public
|
|
372
|
+
// `unregisterApp` would also detach. This mimics the in-process
|
|
373
|
+
// version-update scenario for the reset code path.
|
|
374
|
+
const { registeredApps } = await import('./registry.svelte');
|
|
375
|
+
const existing = registeredApps.get('reset-2');
|
|
376
|
+
registeredApps.set('reset-2', Object.assign(Object.assign({}, existing), { initialLayout: [
|
|
377
|
+
{ name: 'first', tree: makeTree(makeSlotNode('x')) },
|
|
378
|
+
// 'second' removed
|
|
379
|
+
] }));
|
|
380
|
+
resetActivePresetToDefault();
|
|
381
|
+
expect(presetManager.active()).toBe('first');
|
|
382
|
+
// The previously-active 'second' preset is still in the blob (we
|
|
383
|
+
// don't garbage-collect dropped presets); only its name was vacated.
|
|
384
|
+
expect(layoutStore.root).toMatchObject({ type: 'slot', slotId: 'x' });
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// Scenario C.3 — reset throws when no app is attached
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
describe('resetActivePresetToDefault — scenario C.3 no app attached', () => {
|
|
391
|
+
beforeEach(resetFramework);
|
|
392
|
+
it('throws a useful error when called with no attached app', () => {
|
|
393
|
+
expect(() => resetActivePresetToDefault()).toThrow(/no app attached/);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Scenario C.4 — reset re-scopes slot-host refcount holds
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
describe('resetActivePresetToDefault — scenario C.4 slot-host hold accounting', () => {
|
|
400
|
+
beforeEach(resetFramework);
|
|
401
|
+
it('updates appEntry.heldSlotIds to match the rebuilt tree', async () => {
|
|
402
|
+
var _a, _b;
|
|
403
|
+
const { __inspectAppEntryHeldSlotIdsForTest } = await import('../layout/store.svelte');
|
|
404
|
+
registerApp(makeApp({
|
|
405
|
+
manifest: makeAppManifest({ id: 'reset-3' }),
|
|
406
|
+
initialLayout: [
|
|
407
|
+
{
|
|
408
|
+
name: 'main',
|
|
409
|
+
tree: makeTree(makeTabsNode([
|
|
410
|
+
makeTabEntry({ slotId: 't-a', label: 'A' }),
|
|
411
|
+
makeTabEntry({ slotId: 't-b', label: 'B' }),
|
|
412
|
+
])),
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
}));
|
|
416
|
+
await launchApp('reset-3');
|
|
417
|
+
expect((_a = __inspectAppEntryHeldSlotIdsForTest()) === null || _a === void 0 ? void 0 : _a.slice().sort()).toEqual(['t-a', 't-b']);
|
|
418
|
+
// Mutate the live tree by replacing the docked node with a different
|
|
419
|
+
// single slot. After reset, the held set must return to the default
|
|
420
|
+
// pair, which proves the old single hold was released and the
|
|
421
|
+
// canonical pair was re-acquired.
|
|
422
|
+
layoutStore.tree.docked = makeSlotNode('mutated');
|
|
423
|
+
resetActivePresetToDefault();
|
|
424
|
+
expect((_b = __inspectAppEntryHeldSlotIdsForTest()) === null || _b === void 0 ? void 0 : _b.slice().sort()).toEqual(['t-a', 't-b']);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
// Scenario C.5 — sh3.app.reset-layout is registered with the right shape
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
describe('sh3coreShard — sh3.app.reset-layout registration', () => {
|
|
431
|
+
beforeEach(resetFramework);
|
|
432
|
+
it('exposes a "Reset Current Layout" action scoped to app, palette-only', async () => {
|
|
433
|
+
// Register and activate the sh3core pseudo-shard the same way bootstrap()
|
|
434
|
+
// would — tests don't run bootstrap, so the shard's actions wouldn't
|
|
435
|
+
// otherwise be present.
|
|
436
|
+
const { sh3coreShard } = await import('../sh3core-shard/sh3coreShard.svelte');
|
|
437
|
+
const { activateShard } = await import('../shards/activate.svelte');
|
|
438
|
+
const { addAutostartShard } = await import('../actions/state.svelte');
|
|
439
|
+
registerShard(sh3coreShard);
|
|
440
|
+
// Mirror what bootstrap() does: mark the framework shard as autostart so
|
|
441
|
+
// its 'app'-scope actions are treated as ambient inside any app.
|
|
442
|
+
addAutostartShard(sh3coreShard.manifest.id);
|
|
443
|
+
await activateShard(sh3coreShard.manifest.id);
|
|
444
|
+
registerApp(makeApp({
|
|
445
|
+
manifest: makeAppManifest({ id: 'reset-action-1' }),
|
|
446
|
+
initialLayout: [{ name: 'main', tree: makeTree(makeSlotNode('x')) }],
|
|
447
|
+
}));
|
|
448
|
+
await launchApp('reset-action-1');
|
|
449
|
+
const { shell } = await import('../shellRuntime.svelte');
|
|
450
|
+
const actions = shell.actions.listActive();
|
|
451
|
+
const reset = actions.find((a) => a.id === 'sh3.app.reset-layout');
|
|
452
|
+
expect(reset).toBeDefined();
|
|
453
|
+
expect(reset === null || reset === void 0 ? void 0 : reset.label).toBe('Reset Current Layout');
|
|
454
|
+
expect(reset === null || reset === void 0 ? void 0 : reset.paletteItem).toBe(true);
|
|
455
|
+
expect(reset === null || reset === void 0 ? void 0 : reset.contextItem).toBe(false);
|
|
456
|
+
expect(reset === null || reset === void 0 ? void 0 : reset.effectiveShortcut).toBeNull();
|
|
457
|
+
});
|
|
458
|
+
it('hides "app"-scope actions after returnToHome', async () => {
|
|
459
|
+
const { sh3coreShard } = await import('../sh3core-shard/sh3coreShard.svelte');
|
|
460
|
+
const { activateShard } = await import('../shards/activate.svelte');
|
|
461
|
+
const { addAutostartShard } = await import('../actions/state.svelte');
|
|
462
|
+
registerShard(sh3coreShard);
|
|
463
|
+
addAutostartShard(sh3coreShard.manifest.id);
|
|
464
|
+
await activateShard(sh3coreShard.manifest.id);
|
|
465
|
+
registerApp(makeApp({
|
|
466
|
+
manifest: makeAppManifest({ id: 'reset-action-2' }),
|
|
467
|
+
initialLayout: [{ name: 'main', tree: makeTree(makeSlotNode('x')) }],
|
|
468
|
+
}));
|
|
469
|
+
await launchApp('reset-action-2');
|
|
470
|
+
const { shell } = await import('../shellRuntime.svelte');
|
|
471
|
+
expect(shell.actions.listActive().some((a) => a.id === 'sh3.app.reset-layout')).toBe(true);
|
|
472
|
+
await returnToHome();
|
|
473
|
+
expect(shell.actions.listActive().some((a) => a.id === 'sh3.app.reset-layout')).toBe(false);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
// breadcrumbAppId — lingers across returnToHome (distinct from lastAppState)
|
|
478
|
+
// ---------------------------------------------------------------------------
|
|
479
|
+
describe('breadcrumbAppId', () => {
|
|
480
|
+
beforeEach(resetFramework);
|
|
481
|
+
it('starts null when no app has launched this session', async () => {
|
|
482
|
+
const { getBreadcrumbAppId } = await import('./registry.svelte');
|
|
483
|
+
expect(getBreadcrumbAppId()).toBeNull();
|
|
484
|
+
});
|
|
485
|
+
it('is set to the launched app id', async () => {
|
|
486
|
+
const { getBreadcrumbAppId } = await import('./registry.svelte');
|
|
487
|
+
registerShard(makeShard({ manifest: makeShardManifest({ id: 'shard-A' }) }));
|
|
488
|
+
registerApp(makeApp({
|
|
489
|
+
manifest: makeAppManifest({ id: 'app-bc1', requiredShards: ['shard-A'] }),
|
|
490
|
+
}));
|
|
491
|
+
await launchApp('app-bc1');
|
|
492
|
+
expect(getBreadcrumbAppId()).toBe('app-bc1');
|
|
493
|
+
});
|
|
494
|
+
it('lingers across returnToHome (does NOT clear like lastAppState)', async () => {
|
|
495
|
+
const { getBreadcrumbAppId } = await import('./registry.svelte');
|
|
496
|
+
const { readLastApp } = await import('./lifecycle');
|
|
497
|
+
registerShard(makeShard({ manifest: makeShardManifest({ id: 'shard-A' }) }));
|
|
498
|
+
registerApp(makeApp({
|
|
499
|
+
manifest: makeAppManifest({ id: 'app-bc2', requiredShards: ['shard-A'] }),
|
|
500
|
+
}));
|
|
501
|
+
await launchApp('app-bc2');
|
|
502
|
+
await returnToHome();
|
|
503
|
+
expect(getBreadcrumbAppId()).toBe('app-bc2');
|
|
504
|
+
expect(readLastApp()).toBeNull();
|
|
505
|
+
});
|
|
506
|
+
it('overwrites on a subsequent launch', async () => {
|
|
507
|
+
const { getBreadcrumbAppId } = await import('./registry.svelte');
|
|
508
|
+
registerShard(makeShard({ manifest: makeShardManifest({ id: 'shard-A' }) }));
|
|
509
|
+
registerApp(makeApp({
|
|
510
|
+
manifest: makeAppManifest({ id: 'app-bc3a', requiredShards: ['shard-A'] }),
|
|
511
|
+
}));
|
|
512
|
+
registerApp(makeApp({
|
|
513
|
+
manifest: makeAppManifest({ id: 'app-bc3b', requiredShards: ['shard-A'] }),
|
|
514
|
+
}));
|
|
515
|
+
await launchApp('app-bc3a');
|
|
516
|
+
await launchApp('app-bc3b');
|
|
517
|
+
expect(getBreadcrumbAppId()).toBe('app-bc3b');
|
|
518
|
+
});
|
|
519
|
+
});
|
|
@@ -13,6 +13,22 @@ export declare const registeredApps: Map<string, App>;
|
|
|
13
13
|
export declare const activeApp: {
|
|
14
14
|
id: string | null;
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Most recently launched app id this session. Distinct from
|
|
18
|
+
* `lastAppState` in `lifecycle.ts` (which is persisted to the user zone
|
|
19
|
+
* for boot, and cleared by `returnToHome`). The breadcrumb pointer
|
|
20
|
+
* survives `returnToHome` so the top-bar BrandSlot can render
|
|
21
|
+
* `SH3 > [App Name]` and let the user one-click back into the app.
|
|
22
|
+
*
|
|
23
|
+
* In-memory only — page reload resets it to null. No persistence.
|
|
24
|
+
*/
|
|
25
|
+
export declare const breadcrumbApp: {
|
|
26
|
+
id: string | null;
|
|
27
|
+
};
|
|
28
|
+
/** Read the breadcrumb app id. `null` when no app has launched this session. */
|
|
29
|
+
export declare function getBreadcrumbAppId(): string | null;
|
|
30
|
+
/** @internal — test helper; resets the breadcrumb to null. */
|
|
31
|
+
export declare function __resetBreadcrumbForTest(): void;
|
|
16
32
|
/**
|
|
17
33
|
* Register (or re-register) an app with the framework.
|
|
18
34
|
*
|
|
@@ -38,5 +54,5 @@ export declare function getActiveApp(): AppManifest | null;
|
|
|
38
54
|
* activate hook. Not re-exported through `api.ts`.
|
|
39
55
|
*/
|
|
40
56
|
export declare function getRegisteredApp(id: string): App | undefined;
|
|
41
|
-
/** Test-only reset: clear registered apps
|
|
57
|
+
/** Test-only reset: clear registered apps, active-app pointer, and breadcrumb. */
|
|
42
58
|
export declare function __resetAppRegistryForTest(): void;
|
|
@@ -20,6 +20,24 @@ export const registeredApps = $state(new Map());
|
|
|
20
20
|
* most one active app at a time.
|
|
21
21
|
*/
|
|
22
22
|
export const activeApp = $state({ id: null });
|
|
23
|
+
/**
|
|
24
|
+
* Most recently launched app id this session. Distinct from
|
|
25
|
+
* `lastAppState` in `lifecycle.ts` (which is persisted to the user zone
|
|
26
|
+
* for boot, and cleared by `returnToHome`). The breadcrumb pointer
|
|
27
|
+
* survives `returnToHome` so the top-bar BrandSlot can render
|
|
28
|
+
* `SH3 > [App Name]` and let the user one-click back into the app.
|
|
29
|
+
*
|
|
30
|
+
* In-memory only — page reload resets it to null. No persistence.
|
|
31
|
+
*/
|
|
32
|
+
export const breadcrumbApp = $state({ id: null });
|
|
33
|
+
/** Read the breadcrumb app id. `null` when no app has launched this session. */
|
|
34
|
+
export function getBreadcrumbAppId() {
|
|
35
|
+
return breadcrumbApp.id;
|
|
36
|
+
}
|
|
37
|
+
/** @internal — test helper; resets the breadcrumb to null. */
|
|
38
|
+
export function __resetBreadcrumbForTest() {
|
|
39
|
+
breadcrumbApp.id = null;
|
|
40
|
+
}
|
|
23
41
|
/**
|
|
24
42
|
* Register (or re-register) an app with the framework.
|
|
25
43
|
*
|
|
@@ -57,8 +75,9 @@ export function getActiveApp() {
|
|
|
57
75
|
export function getRegisteredApp(id) {
|
|
58
76
|
return registeredApps.get(id);
|
|
59
77
|
}
|
|
60
|
-
/** Test-only reset: clear registered apps
|
|
78
|
+
/** Test-only reset: clear registered apps, active-app pointer, and breadcrumb. */
|
|
61
79
|
export function __resetAppRegistryForTest() {
|
|
62
80
|
registeredApps.clear();
|
|
63
81
|
activeApp.id = null;
|
|
82
|
+
breadcrumbApp.id = null;
|
|
64
83
|
}
|
package/dist/apps/types.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* One menu bar container ("File", "Edit", etc.). Apps declare these in
|
|
6
|
+
* `AppManifest.menus`; when omitted, sh3-core uses DEFAULT_MENU_CONTAINERS.
|
|
7
|
+
* Items reach a container by setting `Action.menuItem` to its `id`.
|
|
8
|
+
*/
|
|
9
|
+
export interface MenuContainer {
|
|
10
|
+
/** Container id referenced by Action.menuItem. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Display label shown in the menu bar. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Optional lucide icon name. */
|
|
15
|
+
icon?: string;
|
|
16
|
+
/** Icon placement relative to the label. Default: 'before'. */
|
|
17
|
+
iconPosition?: 'before' | 'after';
|
|
18
|
+
/**
|
|
19
|
+
* Optional explicit ordering. When omitted, declaration order in the
|
|
20
|
+
* `AppManifest.menus` array is used. Containers with `order` defined
|
|
21
|
+
* sort first (ascending); ties and undefined fall back to declaration
|
|
22
|
+
* order.
|
|
23
|
+
*/
|
|
24
|
+
order?: number;
|
|
25
|
+
}
|
|
4
26
|
/**
|
|
5
27
|
* Static description of an app as observed by the framework at runtime.
|
|
6
28
|
* `version` is always present here: externally installed apps have it
|
|
@@ -46,6 +68,12 @@ export interface AppManifest {
|
|
|
46
68
|
* in a later plan alongside the server-side sync runtime.
|
|
47
69
|
*/
|
|
48
70
|
permissions?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Optional menu bar container list. When present, fully replaces the
|
|
73
|
+
* canonical fallback (file, edit, view, window, help) for this app.
|
|
74
|
+
* When absent, the canonical fallback is used. See MenuContainer.
|
|
75
|
+
*/
|
|
76
|
+
menus?: MenuContainer[];
|
|
49
77
|
}
|
|
50
78
|
/**
|
|
51
79
|
* Context object passed to `App.activate`. Provides app-scoped state zones
|
package/dist/assets/icons.svg
CHANGED
|
@@ -653,6 +653,11 @@
|
|
|
653
653
|
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
654
654
|
</symbol>
|
|
655
655
|
|
|
656
|
+
<!-- lucide/magnet -->
|
|
657
|
+
<symbol id="magnet" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
658
|
+
<path d="m12 15 4 4" /><path d="M2.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.029-6.029a1 1 0 1 1 3 3l-6.029 6.029a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.365-6.367A1 1 0 0 0 8.716 4.282z" />
|
|
659
|
+
<path d="m5 8 4 4" />
|
|
660
|
+
</symbol>
|
|
656
661
|
<!-- lucide/video -->
|
|
657
662
|
<symbol id="video" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
658
663
|
<path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5" />
|
|
@@ -5,6 +5,7 @@ export declare class MemoryDocumentBackend implements DocumentBackend {
|
|
|
5
5
|
read(tenantId: string, shardId: string, path: string): Promise<string | ArrayBuffer | null>;
|
|
6
6
|
write(tenantId: string, shardId: string, path: string, content: string | ArrayBuffer): Promise<void>;
|
|
7
7
|
delete(tenantId: string, shardId: string, path: string): Promise<void>;
|
|
8
|
+
rename(tenantId: string, shardId: string, oldPath: string, newPath: string): Promise<void>;
|
|
8
9
|
list(tenantId: string, shardId: string): Promise<DocumentMeta[]>;
|
|
9
10
|
exists(tenantId: string, shardId: string, path: string): Promise<boolean>;
|
|
10
11
|
listAllShards(tenantId: string): Promise<string[]>;
|
|
@@ -18,6 +19,7 @@ export declare class IndexedDBDocumentBackend implements DocumentBackend {
|
|
|
18
19
|
read(tenantId: string, shardId: string, path: string): Promise<string | ArrayBuffer | null>;
|
|
19
20
|
write(tenantId: string, shardId: string, path: string, content: string | ArrayBuffer): Promise<void>;
|
|
20
21
|
delete(tenantId: string, shardId: string, path: string): Promise<void>;
|
|
22
|
+
rename(tenantId: string, shardId: string, oldPath: string, newPath: string): Promise<void>;
|
|
21
23
|
list(tenantId: string, shardId: string): Promise<DocumentMeta[]>;
|
|
22
24
|
exists(tenantId: string, shardId: string, path: string): Promise<boolean>;
|
|
23
25
|
listAllShards(tenantId: string): Promise<string[]>;
|
|
@@ -45,6 +45,19 @@ export class MemoryDocumentBackend {
|
|
|
45
45
|
async delete(tenantId, shardId, path) {
|
|
46
46
|
__classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").delete(compositeKey(tenantId, shardId, path));
|
|
47
47
|
}
|
|
48
|
+
async rename(tenantId, shardId, oldPath, newPath) {
|
|
49
|
+
const oldKey = compositeKey(tenantId, shardId, oldPath);
|
|
50
|
+
const newKey = compositeKey(tenantId, shardId, newPath);
|
|
51
|
+
const entry = __classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").get(oldKey);
|
|
52
|
+
if (!entry) {
|
|
53
|
+
throw new Error(`Document not found at ${oldPath}`);
|
|
54
|
+
}
|
|
55
|
+
if (__classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").has(newKey)) {
|
|
56
|
+
throw new Error(`Document already exists at ${newPath}`);
|
|
57
|
+
}
|
|
58
|
+
__classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").set(newKey, Object.assign(Object.assign({}, entry), { lastModified: Date.now() }));
|
|
59
|
+
__classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").delete(oldKey);
|
|
60
|
+
}
|
|
48
61
|
async list(tenantId, shardId) {
|
|
49
62
|
const prefix = keyPrefix(tenantId, shardId);
|
|
50
63
|
const out = [];
|
|
@@ -126,6 +139,48 @@ export class IndexedDBDocumentBackend {
|
|
|
126
139
|
const key = compositeKey(tenantId, shardId, path);
|
|
127
140
|
await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_tx).call(this, 'readwrite', (s) => s.delete(key));
|
|
128
141
|
}
|
|
142
|
+
async rename(tenantId, shardId, oldPath, newPath) {
|
|
143
|
+
const oldKey = compositeKey(tenantId, shardId, oldPath);
|
|
144
|
+
const newKey = compositeKey(tenantId, shardId, newPath);
|
|
145
|
+
const db = await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_db).call(this);
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
let aborted = false;
|
|
148
|
+
const tx = db.transaction(IDB_STORE, 'readwrite');
|
|
149
|
+
const store = tx.objectStore(IDB_STORE);
|
|
150
|
+
const getOld = store.get(oldKey);
|
|
151
|
+
getOld.onsuccess = () => {
|
|
152
|
+
const entry = getOld.result;
|
|
153
|
+
if (!entry) {
|
|
154
|
+
aborted = true;
|
|
155
|
+
tx.abort();
|
|
156
|
+
reject(new Error(`Document not found at ${oldPath}`));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const getNew = store.getKey(newKey);
|
|
160
|
+
getNew.onsuccess = () => {
|
|
161
|
+
if (getNew.result !== undefined) {
|
|
162
|
+
aborted = true;
|
|
163
|
+
tx.abort();
|
|
164
|
+
reject(new Error(`Document already exists at ${newPath}`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const renamed = Object.assign(Object.assign({}, entry), { lastModified: Date.now() });
|
|
168
|
+
store.put(renamed, newKey);
|
|
169
|
+
store.delete(oldKey);
|
|
170
|
+
};
|
|
171
|
+
getNew.onerror = () => reject(getNew.error);
|
|
172
|
+
};
|
|
173
|
+
getOld.onerror = () => reject(getOld.error);
|
|
174
|
+
tx.oncomplete = () => resolve();
|
|
175
|
+
tx.onerror = () => {
|
|
176
|
+
if (!aborted)
|
|
177
|
+
reject(tx.error);
|
|
178
|
+
};
|
|
179
|
+
tx.onabort = () => {
|
|
180
|
+
// Already rejected above; swallow.
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
129
184
|
async list(tenantId, shardId) {
|
|
130
185
|
const prefix = keyPrefix(tenantId, shardId);
|
|
131
186
|
const db = await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_db).call(this);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import 'fake-indexeddb/auto';
|