@happyvertical/smrt-svelte 0.38.5 → 0.38.6
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/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte +52 -0
- package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts +23 -0
- package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts.map +1 -0
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +130 -0
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +2 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +161 -83
- package/package.json +5 -5
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Runs a caller-supplied ShellState mutation inside a consumer $effect. Tests
|
|
4
|
+
* use this to prove ShellState's internal reads do not become dependencies of
|
|
5
|
+
* the consumer effect.
|
|
6
|
+
*/
|
|
7
|
+
import type { ShellState } from '../admin-shell/state.svelte.js';
|
|
8
|
+
import type { ShellActivity } from '../admin-shell/types.js';
|
|
9
|
+
|
|
10
|
+
type EffectCleanup = () => void;
|
|
11
|
+
|
|
12
|
+
interface MutationInputs {
|
|
13
|
+
activity: ShellActivity;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
shell: ShellState;
|
|
18
|
+
run: (shell: ShellState, inputs: MutationInputs) => EffectCleanup | undefined;
|
|
19
|
+
onReady: (controls: {
|
|
20
|
+
getRuns: () => number;
|
|
21
|
+
setActivityProgress: (progress: number) => void;
|
|
22
|
+
}) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props: Props = $props();
|
|
26
|
+
const activity = $state<ShellActivity>({
|
|
27
|
+
id: 'reactive-sync',
|
|
28
|
+
label: 'Reactive sync',
|
|
29
|
+
kind: 'sync',
|
|
30
|
+
scope: 'system',
|
|
31
|
+
status: 'running',
|
|
32
|
+
progress: 0,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Plain let on purpose: making this $state would add tracking noise to the
|
|
36
|
+
// effect-leak signal this harness exists to measure.
|
|
37
|
+
let runs = 0;
|
|
38
|
+
|
|
39
|
+
$effect(() => {
|
|
40
|
+
runs += 1;
|
|
41
|
+
return props.run(props.shell, { activity });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// This harness intentionally snapshots the callback during setup.
|
|
45
|
+
// svelte-ignore state_referenced_locally
|
|
46
|
+
props.onReady({
|
|
47
|
+
getRuns: () => runs,
|
|
48
|
+
setActivityProgress: (progress: number) => {
|
|
49
|
+
activity.progress = progress;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs a caller-supplied ShellState mutation inside a consumer $effect. Tests
|
|
3
|
+
* use this to prove ShellState's internal reads do not become dependencies of
|
|
4
|
+
* the consumer effect.
|
|
5
|
+
*/
|
|
6
|
+
import type { ShellState } from '../admin-shell/state.svelte.js';
|
|
7
|
+
import type { ShellActivity } from '../admin-shell/types.js';
|
|
8
|
+
type EffectCleanup = () => void;
|
|
9
|
+
interface MutationInputs {
|
|
10
|
+
activity: ShellActivity;
|
|
11
|
+
}
|
|
12
|
+
interface Props {
|
|
13
|
+
shell: ShellState;
|
|
14
|
+
run: (shell: ShellState, inputs: MutationInputs) => EffectCleanup | undefined;
|
|
15
|
+
onReady: (controls: {
|
|
16
|
+
getRuns: () => number;
|
|
17
|
+
setActivityProgress: (progress: number) => void;
|
|
18
|
+
}) => void;
|
|
19
|
+
}
|
|
20
|
+
declare const AdminShellMutationEffectHarness: import("svelte").Component<Props, {}, "">;
|
|
21
|
+
type AdminShellMutationEffectHarness = ReturnType<typeof AdminShellMutationEffectHarness>;
|
|
22
|
+
export default AdminShellMutationEffectHarness;
|
|
23
|
+
//# sourceMappingURL=admin-shell-mutation-effect-harness.svelte.d.ts.map
|
package/dist/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-shell-mutation-effect-harness.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/__tests__/admin-shell-mutation-effect-harness.svelte.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,KAAK,aAAa,GAAG,MAAM,IAAI,CAAC;AAEhC,UAAU,cAAc;IACtB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,UAAU,KAAK;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,KAAK,aAAa,GAAG,SAAS,CAAC;IAC9E,OAAO,EAAE,CAAC,QAAQ,EAAE;QAClB,OAAO,EAAE,MAAM,MAAM,CAAC;QACtB,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;KACjD,KAAK,IAAI,CAAC;CACZ;AAwCD,QAAA,MAAM,+BAA+B,2CAAwC,CAAC;AAC9E,KAAK,+BAA+B,GAAG,UAAU,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAC1F,eAAe,+BAA+B,CAAC"}
|
|
@@ -1,5 +1,32 @@
|
|
|
1
|
+
import { flushSync, mount, unmount } from 'svelte';
|
|
1
2
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
3
|
import { createShellState } from '../admin-shell/state.svelte.js';
|
|
4
|
+
import MutationEffectHarness from './admin-shell-mutation-effect-harness.svelte';
|
|
5
|
+
function mountMutationEffect(shell, run) {
|
|
6
|
+
const target = document.createElement('div');
|
|
7
|
+
document.body.appendChild(target);
|
|
8
|
+
let getRuns = () => 0;
|
|
9
|
+
let setActivityProgress = (_progress) => { };
|
|
10
|
+
const component = mount(MutationEffectHarness, {
|
|
11
|
+
target,
|
|
12
|
+
props: {
|
|
13
|
+
shell,
|
|
14
|
+
run,
|
|
15
|
+
onReady: (controls) => {
|
|
16
|
+
getRuns = controls.getRuns;
|
|
17
|
+
setActivityProgress = controls.setActivityProgress;
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
getRuns,
|
|
23
|
+
setActivityProgress,
|
|
24
|
+
teardown: () => {
|
|
25
|
+
unmount(component);
|
|
26
|
+
target.remove();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
3
30
|
describe('ShellState', () => {
|
|
4
31
|
it('allows panels to expand independently by default', () => {
|
|
5
32
|
const shell = createShellState();
|
|
@@ -107,4 +134,107 @@ describe('ShellState', () => {
|
|
|
107
134
|
// upsert carrying `previous`, which the toaster filters out.
|
|
108
135
|
expect(events).toEqual(['upsert:new', 'upsert:has-prev']);
|
|
109
136
|
});
|
|
137
|
+
it('does not leak focus-tool state reads into consumer effects', () => {
|
|
138
|
+
const shell = createShellState();
|
|
139
|
+
const harness = mountMutationEffect(shell, (state) => {
|
|
140
|
+
const unregister = state.registerFocusTool({
|
|
141
|
+
id: 'activity',
|
|
142
|
+
label: 'Activity',
|
|
143
|
+
});
|
|
144
|
+
state.openFocusTool('activity');
|
|
145
|
+
return unregister;
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
flushSync();
|
|
149
|
+
expect(harness.getRuns()).toBe(1);
|
|
150
|
+
shell.collapsePanel('right');
|
|
151
|
+
flushSync();
|
|
152
|
+
shell.openFocusTool('activity');
|
|
153
|
+
flushSync();
|
|
154
|
+
expect(harness.getRuns()).toBe(1);
|
|
155
|
+
expect(shell.focusTools.map((tool) => tool.id)).toEqual(['activity']);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
harness.teardown();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
it('does not leak panel state reads into consumer effects', () => {
|
|
162
|
+
const shell = createShellState();
|
|
163
|
+
const harness = mountMutationEffect(shell, (state) => {
|
|
164
|
+
state.setPanel('right', 'expanded');
|
|
165
|
+
return undefined;
|
|
166
|
+
});
|
|
167
|
+
try {
|
|
168
|
+
flushSync();
|
|
169
|
+
expect(harness.getRuns()).toBe(1);
|
|
170
|
+
shell.togglePanel('right');
|
|
171
|
+
flushSync();
|
|
172
|
+
expect(harness.getRuns()).toBe(1);
|
|
173
|
+
expect(shell.panels.right).toBe('collapsed');
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
harness.teardown();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
it('does not leak settings state reads into consumer effects', () => {
|
|
180
|
+
const shell = createShellState();
|
|
181
|
+
const harness = mountMutationEffect(shell, (state) => {
|
|
182
|
+
state.applySettings({ panels: { right: 'expanded' } });
|
|
183
|
+
return undefined;
|
|
184
|
+
});
|
|
185
|
+
try {
|
|
186
|
+
flushSync();
|
|
187
|
+
expect(harness.getRuns()).toBe(1);
|
|
188
|
+
shell.setHotkey('right', 'KeyR');
|
|
189
|
+
flushSync();
|
|
190
|
+
expect(harness.getRuns()).toBe(1);
|
|
191
|
+
expect(shell.settings.keymap?.right).toEqual({ code: 'KeyR' });
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
harness.teardown();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
it('does not leak activity state reads into consumer effects', () => {
|
|
198
|
+
const shell = createShellState();
|
|
199
|
+
const harness = mountMutationEffect(shell, (state) => {
|
|
200
|
+
state.upsertActivity({
|
|
201
|
+
id: 'sync-1',
|
|
202
|
+
label: 'Sync',
|
|
203
|
+
kind: 'sync',
|
|
204
|
+
scope: 'system',
|
|
205
|
+
status: 'running',
|
|
206
|
+
});
|
|
207
|
+
return undefined;
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
flushSync();
|
|
211
|
+
expect(harness.getRuns()).toBe(1);
|
|
212
|
+
shell.updateActivity('sync-1', { progress: 50 });
|
|
213
|
+
flushSync();
|
|
214
|
+
expect(harness.getRuns()).toBe(1);
|
|
215
|
+
expect(shell.listActivities()[0]?.progress).toBe(50);
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
harness.teardown();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
it('keeps caller-supplied reactive activity inputs tracked', () => {
|
|
222
|
+
const shell = createShellState();
|
|
223
|
+
const harness = mountMutationEffect(shell, (state, inputs) => {
|
|
224
|
+
state.upsertActivity(inputs.activity);
|
|
225
|
+
return undefined;
|
|
226
|
+
});
|
|
227
|
+
try {
|
|
228
|
+
flushSync();
|
|
229
|
+
expect(harness.getRuns()).toBe(1);
|
|
230
|
+
expect(shell.listActivities()[0]?.progress).toBe(0);
|
|
231
|
+
harness.setActivityProgress(75);
|
|
232
|
+
flushSync();
|
|
233
|
+
expect(harness.getRuns()).toBe(2);
|
|
234
|
+
expect(shell.listActivities()[0]?.progress).toBe(75);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
harness.teardown();
|
|
238
|
+
}
|
|
239
|
+
});
|
|
110
240
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PanelEdge, PanelState, ResolvedShellConfig, ShellActivity, ShellActivityBadge, ShellActivityEvent, ShellActivityFilter, ShellFocusTool, ShellPanelDefaults, ShellScope, ShellSettingsAdapter, ShellSettingsDelta, ShellStateSnapshot, VisiblePanelState } from './types.js';
|
|
2
2
|
type ActivityListener = (event: ShellActivityEvent) => void;
|
|
3
|
+
type ShellActivityPatch = Partial<Omit<ShellActivity, 'id'>>;
|
|
3
4
|
export interface ShellStateOptions {
|
|
4
5
|
config?: ShellPanelDefaults;
|
|
5
6
|
settings?: ShellSettingsDelta;
|
|
@@ -32,7 +33,7 @@ export declare class ShellState {
|
|
|
32
33
|
unregisterFocusTool(id: string): void;
|
|
33
34
|
openFocusTool(id: string): void;
|
|
34
35
|
upsertActivity(activity: ShellActivity): void;
|
|
35
|
-
updateActivity(id: string, patch:
|
|
36
|
+
updateActivity(id: string, patch: ShellActivityPatch): void;
|
|
36
37
|
removeActivity(id: string): void;
|
|
37
38
|
watchActivities(listener: ActivityListener): () => void;
|
|
38
39
|
listActivities(filter?: ShellActivityFilter): ShellActivity[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/state.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/state.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAGpB,KAAK,gBAAgB,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAC5D,KAAK,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,UAAU;IACrB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAE9C,QAAQ,qBAAkC;IAC1C,MAAM,gCAKH;IACH,UAAU,mBAAgC;IAC1C,iBAAiB,gBAA+B;IAChD,UAAU,kBAA+B;IAEzC,OAAO,CAAC,iBAAiB,CAA+B;gBAE5C,OAAO,GAAE,iBAAsB;IAUrC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,QAAQ,IAAI,kBAAkB;IAY9B,aAAa,CACX,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,IAAI;IAkBP,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIzC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAUrD,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAezD,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAUlC,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAIlC,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAIpC,oBAAoB,IAAI,OAAO;IAY/B,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,IAAI;IAYnD,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IASrC,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAY/B,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IA4B7C,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAc3D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAWhC,eAAe,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAKvD,cAAc,CAAC,MAAM,GAAE,mBAAwB,GAAG,aAAa,EAAE;IASjE,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,kBAAkB;IAsBlD,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS;IAW9C,OAAO,CAAC,mBAAmB;YAmBb,eAAe;IAI7B,OAAO,CAAC,YAAY;CAGrB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAE5E"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { untrack } from 'svelte';
|
|
1
2
|
import { LocalStorageShellSettingsAdapter, mergeShellSettingsDelta, resolveInitialPanelState, resolveShellConfig, } from './settings.js';
|
|
2
3
|
import { PANEL_EDGES, SCOPE_EDGES } from './types.js';
|
|
3
4
|
export class ShellState {
|
|
@@ -42,121 +43,148 @@ export class ShellState {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
applySettings(delta, options = {}) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const nextDelta = snapshotShellSettingsDelta(delta);
|
|
47
|
+
const persist = options.persist;
|
|
48
|
+
untrack(() => {
|
|
49
|
+
this.settings = mergeShellSettingsDelta(this.settings, nextDelta);
|
|
50
|
+
for (const edge of PANEL_EDGES) {
|
|
51
|
+
this.panels[edge] = resolveInitialPanelState(edge, this.config.panels[edge], this.settings);
|
|
52
|
+
}
|
|
53
|
+
this.activeFocusToolId =
|
|
54
|
+
this.settings.activeFocusToolId ?? this.activeFocusToolId;
|
|
55
|
+
if (persist !== false)
|
|
56
|
+
void this.persistSettings();
|
|
57
|
+
});
|
|
53
58
|
}
|
|
54
59
|
setHotkeysEnabled(enabled) {
|
|
55
|
-
this.applySettings({ hotkeysEnabled: enabled });
|
|
60
|
+
untrack(() => this.applySettings({ hotkeysEnabled: enabled }));
|
|
56
61
|
}
|
|
57
62
|
setHotkey(edge, code) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
untrack(() => {
|
|
64
|
+
this.applySettings({
|
|
65
|
+
keymap: {
|
|
66
|
+
[edge]: code ? { code } : null,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
62
69
|
});
|
|
63
70
|
}
|
|
64
71
|
setPanel(edge, state) {
|
|
65
|
-
|
|
66
|
-
this.panels[edge]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
untrack(() => {
|
|
73
|
+
if (this.config.panels[edge].initial === 'hidden') {
|
|
74
|
+
this.panels[edge] = 'hidden';
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (state === 'expanded')
|
|
78
|
+
this.closeExclusivePeers(edge);
|
|
79
|
+
this.panels[edge] = state;
|
|
80
|
+
this.settings = mergeShellSettingsDelta(this.settings, {
|
|
81
|
+
panels: { [edge]: state },
|
|
82
|
+
});
|
|
83
|
+
void this.persistSettings();
|
|
74
84
|
});
|
|
75
|
-
void this.persistSettings();
|
|
76
85
|
}
|
|
77
86
|
togglePanel(edge) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
untrack(() => {
|
|
88
|
+
if (this.panels[edge] === 'hidden')
|
|
89
|
+
return;
|
|
90
|
+
this.setPanel(edge, this.panels[edge] === 'expanded' ? 'collapsed' : 'expanded');
|
|
91
|
+
});
|
|
81
92
|
}
|
|
82
93
|
expandPanel(edge) {
|
|
83
|
-
this.setPanel(edge, 'expanded');
|
|
94
|
+
untrack(() => this.setPanel(edge, 'expanded'));
|
|
84
95
|
}
|
|
85
96
|
collapsePanel(edge) {
|
|
86
|
-
this.setPanel(edge, 'collapsed');
|
|
97
|
+
untrack(() => this.setPanel(edge, 'collapsed'));
|
|
87
98
|
}
|
|
88
99
|
closeTopmostExpanded() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.
|
|
92
|
-
|
|
100
|
+
return untrack(() => {
|
|
101
|
+
for (const edge of [...PANEL_EDGES].reverse()) {
|
|
102
|
+
if (this.panels[edge] === 'expanded') {
|
|
103
|
+
this.collapsePanel(edge);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
93
106
|
}
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
return false;
|
|
108
|
+
});
|
|
96
109
|
}
|
|
97
110
|
registerFocusTool(tool) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
const nextTool = snapshotFocusTool(tool);
|
|
112
|
+
return untrack(() => {
|
|
113
|
+
this.focusTools = [
|
|
114
|
+
...this.focusTools.filter((existing) => existing.id !== nextTool.id),
|
|
115
|
+
nextTool,
|
|
116
|
+
].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
117
|
+
if (!this.activeFocusToolId)
|
|
118
|
+
this.activeFocusToolId = nextTool.id;
|
|
119
|
+
return () => this.unregisterFocusTool(nextTool.id);
|
|
120
|
+
});
|
|
105
121
|
}
|
|
106
122
|
unregisterFocusTool(id) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.activeFocusToolId
|
|
110
|
-
|
|
123
|
+
untrack(() => {
|
|
124
|
+
this.focusTools = this.focusTools.filter((tool) => tool.id !== id);
|
|
125
|
+
if (this.activeFocusToolId === id) {
|
|
126
|
+
this.activeFocusToolId = this.focusTools[0]?.id ?? null;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
111
129
|
}
|
|
112
130
|
openFocusTool(id) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
untrack(() => {
|
|
132
|
+
if (!this.focusTools.some((tool) => tool.id === id))
|
|
133
|
+
return;
|
|
134
|
+
this.activeFocusToolId = id;
|
|
135
|
+
this.settings = mergeShellSettingsDelta(this.settings, {
|
|
136
|
+
activeFocusToolId: id,
|
|
137
|
+
});
|
|
138
|
+
this.expandPanel('right');
|
|
139
|
+
void this.persistSettings();
|
|
118
140
|
});
|
|
119
|
-
this.expandPanel('right');
|
|
120
|
-
void this.persistSettings();
|
|
121
141
|
}
|
|
122
142
|
upsertActivity(activity) {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
:
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
const nextActivity = snapshotActivity(activity);
|
|
144
|
+
untrack(() => {
|
|
145
|
+
const now = new Date().toISOString();
|
|
146
|
+
const normalized = {
|
|
147
|
+
...nextActivity,
|
|
148
|
+
edge: nextActivity.edge ?? this.homeEdgeForScope(nextActivity.scope),
|
|
149
|
+
createdAt: nextActivity.createdAt ?? now,
|
|
150
|
+
updatedAt: nextActivity.updatedAt ?? now,
|
|
151
|
+
};
|
|
152
|
+
const previous = this.activities.find((item) => item.id === nextActivity.id);
|
|
153
|
+
this.activities = [
|
|
154
|
+
...this.activities.filter((item) => item.id !== nextActivity.id),
|
|
155
|
+
normalized,
|
|
156
|
+
];
|
|
157
|
+
this.emitActivity({
|
|
158
|
+
type: previous && previous.status !== normalized.status
|
|
159
|
+
? 'transition'
|
|
160
|
+
: 'upsert',
|
|
161
|
+
activity: normalized,
|
|
162
|
+
previous,
|
|
163
|
+
});
|
|
141
164
|
});
|
|
142
165
|
}
|
|
143
166
|
updateActivity(id, patch) {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
const nextPatch = snapshotActivityPatch(patch);
|
|
168
|
+
untrack(() => {
|
|
169
|
+
const current = this.activities.find((activity) => activity.id === id);
|
|
170
|
+
if (!current)
|
|
171
|
+
return;
|
|
172
|
+
this.upsertActivity({
|
|
173
|
+
...current,
|
|
174
|
+
...nextPatch,
|
|
175
|
+
id,
|
|
176
|
+
updatedAt: new Date().toISOString(),
|
|
177
|
+
});
|
|
152
178
|
});
|
|
153
179
|
}
|
|
154
180
|
removeActivity(id) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
untrack(() => {
|
|
182
|
+
const current = this.activities.find((activity) => activity.id === id);
|
|
183
|
+
if (!current)
|
|
184
|
+
return;
|
|
185
|
+
this.activities = this.activities.filter((activity) => activity.id !== id);
|
|
186
|
+
this.emitActivity({ type: 'remove', activity: current });
|
|
187
|
+
});
|
|
160
188
|
}
|
|
161
189
|
watchActivities(listener) {
|
|
162
190
|
this.activityListeners.add(listener);
|
|
@@ -250,3 +278,53 @@ function matchesOne(value, expected) {
|
|
|
250
278
|
function isActiveStatus(status) {
|
|
251
279
|
return status === 'queued' || status === 'running';
|
|
252
280
|
}
|
|
281
|
+
function snapshotShellSettingsDelta(delta) {
|
|
282
|
+
const snapshot = { ...delta };
|
|
283
|
+
if ('keymap' in delta) {
|
|
284
|
+
snapshot.keymap = snapshotShellKeymap(delta.keymap);
|
|
285
|
+
}
|
|
286
|
+
if ('panels' in delta) {
|
|
287
|
+
snapshot.panels = delta.panels ? { ...delta.panels } : delta.panels;
|
|
288
|
+
}
|
|
289
|
+
return snapshot;
|
|
290
|
+
}
|
|
291
|
+
function snapshotShellKeymap(keymap) {
|
|
292
|
+
if (!keymap)
|
|
293
|
+
return keymap;
|
|
294
|
+
const snapshot = {};
|
|
295
|
+
for (const edge of PANEL_EDGES) {
|
|
296
|
+
if (edge in keymap) {
|
|
297
|
+
const binding = keymap[edge];
|
|
298
|
+
snapshot[edge] = binding ? { ...binding } : binding;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return snapshot;
|
|
302
|
+
}
|
|
303
|
+
function snapshotFocusTool(tool) {
|
|
304
|
+
const snapshot = { ...tool };
|
|
305
|
+
if ('subject' in tool) {
|
|
306
|
+
snapshot.subject = tool.subject ? { ...tool.subject } : tool.subject;
|
|
307
|
+
}
|
|
308
|
+
if ('activityKinds' in tool) {
|
|
309
|
+
snapshot.activityKinds = tool.activityKinds
|
|
310
|
+
? [...tool.activityKinds]
|
|
311
|
+
: tool.activityKinds;
|
|
312
|
+
}
|
|
313
|
+
return snapshot;
|
|
314
|
+
}
|
|
315
|
+
function snapshotActivity(activity) {
|
|
316
|
+
const snapshot = { ...activity };
|
|
317
|
+
if ('subject' in activity) {
|
|
318
|
+
snapshot.subject = activity.subject
|
|
319
|
+
? { ...activity.subject }
|
|
320
|
+
: activity.subject;
|
|
321
|
+
}
|
|
322
|
+
return snapshot;
|
|
323
|
+
}
|
|
324
|
+
function snapshotActivityPatch(patch) {
|
|
325
|
+
const snapshot = { ...patch };
|
|
326
|
+
if ('subject' in patch) {
|
|
327
|
+
snapshot.subject = patch.subject ? { ...patch.subject } : patch.subject;
|
|
328
|
+
}
|
|
329
|
+
return snapshot;
|
|
330
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-svelte",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.6",
|
|
4
4
|
"description": "Svelte 5 components for SMRT user management - auth, users, tenants, roles, permissions, groups",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"smrtRawPrimitives": "strict",
|
|
@@ -92,10 +92,10 @@
|
|
|
92
92
|
"@tanstack/db": "^0.6.14",
|
|
93
93
|
"@tanstack/svelte-db": "^0.1.91",
|
|
94
94
|
"esm-env": "^1.2.2",
|
|
95
|
-
"@happyvertical/smrt-
|
|
96
|
-
"@happyvertical/smrt-
|
|
97
|
-
"@happyvertical/smrt-ui": "0.38.
|
|
98
|
-
"@happyvertical/smrt-web": "0.38.
|
|
95
|
+
"@happyvertical/smrt-types": "0.38.6",
|
|
96
|
+
"@happyvertical/smrt-languages": "0.38.6",
|
|
97
|
+
"@happyvertical/smrt-ui": "0.38.6",
|
|
98
|
+
"@happyvertical/smrt-web": "0.38.6"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
101
|
"@huggingface/transformers": ">=3.8.1",
|