zidane-ui 9.1.3 → 9.1.5
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/adapters/server-extensions-host.js +1 -1
- package/dist/adapters/server-host.js +2 -1
- package/dist/adapters/server-machines-host.js +1 -1
- package/dist/adapters/server-session-host.js +87 -13
- package/dist/adapters/server-settings-host.js +1 -1
- package/dist/appearance/AppearanceProvider.js +2 -2
- package/dist/appearance/headless/index.js +1 -1
- package/dist/approvals/headless/index.js +2 -2
- package/dist/chat/HostTranscript.d.ts +5 -1
- package/dist/chat/HostTranscript.js +16 -4
- package/dist/chat/RunEstimateChip.d.ts +5 -3
- package/dist/chat/RunEstimateChip.js +33 -3
- package/dist/chat/Transcript.js +1 -1
- package/dist/chat/composer/ComposerEditor.js +1 -1
- package/dist/chat/composer/ConversationBadges.js +16 -10
- package/dist/chat/composer/composer-bus.d.ts +2 -2
- package/dist/chat/composer/composer-bus.js +2 -2
- package/dist/chat/composer/use-composer-stores.js +1 -1
- package/dist/chat/composer/use-run-meter.js +1 -1
- package/dist/chat/headless/composer/conversation-badges.d.ts +8 -3
- package/dist/chat/headless/composer/conversation-badges.js +26 -11
- package/dist/chat/headless/composer/index.js +2 -2
- package/dist/chat/headless/composer/labels-chrome.d.ts +3 -1
- package/dist/chat/headless/composer/labels-chrome.js +3 -1
- package/dist/chat/headless/index.d.ts +5 -3
- package/dist/chat/headless/index.js +17 -15
- package/dist/chat/headless/labels.d.ts +54 -1
- package/dist/chat/headless/labels.js +62 -2
- package/dist/chat/headless/secrets.d.ts +32 -0
- package/dist/chat/headless/secrets.js +43 -0
- package/dist/chat/headless/session-list.d.ts +45 -0
- package/dist/chat/headless/sidebar/estimates.js +13 -1
- package/dist/chat/headless/sidebar/index.d.ts +2 -2
- package/dist/chat/headless/sidebar/index.js +1 -1
- package/dist/chat/headless/sidebar/labels.d.ts +50 -0
- package/dist/chat/headless/sidebar/labels.js +1 -0
- package/dist/chat/headless/sidebar/query.d.ts +2 -2
- package/dist/chat/headless/sidebar/query.js +6 -3
- package/dist/chat/headless/sidebar/types.d.ts +7 -1
- package/dist/chat/headless/sidebar/view.js +2 -0
- package/dist/chat/headless/transcript/auth-failure.d.ts +67 -0
- package/dist/chat/headless/transcript/auth-failure.js +87 -0
- package/dist/chat/headless/transcript/index.d.ts +2 -1
- package/dist/chat/headless/transcript/index.js +2 -1
- package/dist/chat/headless/transcript/nesting.d.ts +3 -3
- package/dist/chat/headless/transcript/nesting.js +12 -4
- package/dist/chat/index.d.ts +4 -2
- package/dist/chat/index.js +3 -1
- package/dist/chat/interactions/InboxPanel.d.ts +6 -2
- package/dist/chat/interactions/InboxPanel.js +82 -44
- package/dist/chat/interactions/PlanCard.d.ts +2 -2
- package/dist/chat/interactions/PlanCard.js +1 -1
- package/dist/chat/interactions/QuestionField.d.ts +8 -3
- package/dist/chat/interactions/QuestionField.js +89 -8
- package/dist/chat/interactions/QuestionForm.d.ts +3 -1
- package/dist/chat/interactions/QuestionForm.js +48 -10
- package/dist/chat/interactions/SecretInput.d.ts +32 -0
- package/dist/chat/interactions/SecretInput.js +72 -0
- package/dist/chat/interactions/card-ui.d.ts +5 -3
- package/dist/chat/interactions/card-ui.js +5 -3
- package/dist/chat/interactions/index.d.ts +2 -1
- package/dist/chat/interactions/index.js +2 -1
- package/dist/chat/sidebar/SessionSidebar.js +2 -0
- package/dist/chat/sidebar/SidebarFilterMenu.d.ts +7 -2
- package/dist/chat/sidebar/SidebarFilterMenu.js +26 -2
- package/dist/chat/sidebar/SidebarSessionRow.d.ts +3 -1
- package/dist/chat/sidebar/SidebarSessionRow.js +8 -3
- package/dist/chat/sidebar/sidebar-live.js +8 -4
- package/dist/chat/transcript/AuthFailureCard.d.ts +34 -0
- package/dist/chat/transcript/AuthFailureCard.js +358 -0
- package/dist/chat/transcript/InteractionToolBody.js +12 -2
- package/dist/chat/transcript/actions.d.ts +24 -1
- package/dist/chat/transcript/index.d.ts +3 -2
- package/dist/chat/transcript/index.js +2 -1
- package/dist/chat/transcript/notices.d.ts +2 -1
- package/dist/chat/transcript/notices.js +13 -1
- package/dist/chat/transcript/script-input.d.ts +1 -1
- package/dist/chat/transcript/shell-block.js +1 -1
- package/dist/components/HowItWorks.d.ts +8 -1
- package/dist/components/HowItWorks.js +2 -2
- package/dist/components/RowActionsMenu.d.ts +6 -1
- package/dist/components/RowActionsMenu.js +4 -0
- package/dist/core.css +24 -0
- package/dist/dist/{index-WlE93Q6d.d.ts → index-BI6RZc60.d.ts} +36 -3
- package/dist/dist/index-f-XWIoYo.d.ts +1 -0
- package/dist/dist/index.d.ts +6 -0
- package/dist/dist/sandbox-B4OW6oNx.d.ts +2 -0
- package/dist/dist/timeout-B5pO5I5a.d.ts +30 -0
- package/dist/extensions/ExtensionSettingsPage.js +1 -1
- package/dist/extensions/ExtensionSettingsSidebar.js +1 -1
- package/dist/extensions/headless/fixture-host.js +1 -1
- package/dist/extensions/headless/index.js +4 -4
- package/dist/extensions/hooks.js +1 -1
- package/dist/files/DiffsTab.js +1 -1
- package/dist/files/EditorColumn.js +1 -1
- package/dist/files/MediaDiff.js +1 -1
- package/dist/files/SearchPanel.js +121 -53
- package/dist/files/headless/name-match.js +29 -14
- package/dist/files/useEntryActions.js +1 -1
- package/dist/files/useOpenFiles.js +1 -1
- package/dist/git/WherePicker.js +2 -2
- package/dist/git/headless/environment.d.ts +6 -1
- package/dist/git/headless/environment.js +5 -2
- package/dist/git/headless/labels.d.ts +2 -0
- package/dist/git/headless/labels.js +2 -0
- package/dist/git/index.d.ts +2 -1
- package/dist/git/index.js +2 -1
- package/dist/git/useStartFromPanel.d.ts +22 -0
- package/dist/git/useStartFromPanel.js +91 -0
- package/dist/keybindings/KeyCommands.js +1 -1
- package/dist/keybindings/KeybindingsProvider.js +1 -1
- package/dist/keybindings/headless/catalog.js +2 -2
- package/dist/keybindings/headless/index.js +2 -2
- package/dist/keybindings/headless/overrides.js +1 -1
- package/dist/machines/HostSyncPanel.js +1 -1
- package/dist/machines/SyncOutcomeBadge.js +1 -1
- package/dist/machines/headless/index.js +1 -1
- package/dist/onboarding/demos/features.d.ts +9 -1
- package/dist/onboarding/demos/features.js +107 -2
- package/dist/onboarding/demos/how-it-works.d.ts +5 -1
- package/dist/onboarding/demos/how-it-works.js +120 -2
- package/dist/onboarding/headless/labels.d.ts +17 -0
- package/dist/onboarding/headless/labels.js +17 -0
- package/dist/onboarding/index.js +1 -1
- package/dist/onboarding/marks.d.ts +1 -2
- package/dist/onboarding/marks.js +4 -12
- package/dist/onboarding/steps/ReadyStep.js +1 -1
- package/dist/onboarding/steps/connect/ProviderPanel.js +1 -1
- package/dist/onboarding/tutorials.d.ts +3 -1
- package/dist/onboarding/tutorials.js +2 -1
- package/dist/palette/app-sources.js +1 -1
- package/dist/session/ArtifactsPanel.js +1 -1
- package/dist/session/PanelAttention.d.ts +14 -6
- package/dist/session/PanelAttention.js +35 -24
- package/dist/session/PlanTracker.d.ts +54 -0
- package/dist/session/PlanTracker.js +412 -0
- package/dist/session/ProjectHome.d.ts +15 -2
- package/dist/session/ProjectHome.js +158 -85
- package/dist/session/RemindersPanel.d.ts +25 -0
- package/dist/session/RemindersPanel.js +526 -0
- package/dist/session/SecretsPanel.d.ts +19 -0
- package/dist/session/SecretsPanel.js +241 -0
- package/dist/session/SessionProvider.d.ts +26 -5
- package/dist/session/SessionProvider.js +51 -6
- package/dist/session/StepStatusIcon.d.ts +24 -0
- package/dist/session/StepStatusIcon.js +50 -0
- package/dist/session/TodoList.d.ts +4 -3
- package/dist/session/TodoList.js +6 -5
- package/dist/session/headless/fixture-host.d.ts +32 -22
- package/dist/session/headless/fixture-host.js +86 -51
- package/dist/session/headless/fixture-plans.d.ts +57 -0
- package/dist/session/headless/fixture-plans.js +141 -0
- package/dist/session/headless/fixture-schedules.d.ts +49 -0
- package/dist/session/headless/fixture-schedules.js +133 -0
- package/dist/session/headless/home-layout.d.ts +10 -2
- package/dist/session/headless/home-layout.js +7 -2
- package/dist/session/headless/host.d.ts +85 -13
- package/dist/session/headless/index.d.ts +5 -3
- package/dist/session/headless/index.js +6 -4
- package/dist/session/headless/labels.d.ts +95 -12
- package/dist/session/headless/labels.js +67 -18
- package/dist/session/headless/plans.d.ts +53 -0
- package/dist/session/headless/plans.js +78 -0
- package/dist/session/headless/reminder-labels.d.ts +63 -0
- package/dist/session/headless/reminder-labels.js +84 -0
- package/dist/session/headless/schedules.d.ts +93 -0
- package/dist/session/headless/schedules.js +190 -0
- package/dist/session/headless/secrets.d.ts +28 -0
- package/dist/session/headless/secrets.js +48 -0
- package/dist/session/index.d.ts +7 -5
- package/dist/session/index.js +6 -4
- package/dist/session/testing/contract.d.ts +18 -5
- package/dist/session/testing/contract.js +351 -43
- package/dist/session/testing/index.d.ts +4 -2
- package/dist/session/testing/index.js +4 -2
- package/dist/settings/headless/catalog.js +17 -1
- package/dist/settings/headless/index.d.ts +2 -2
- package/dist/settings/headless/index.js +3 -3
- package/dist/settings/headless/labels.d.ts +0 -2
- package/dist/settings/headless/labels.js +0 -2
- package/dist/settings/headless/layout.js +7 -1
- package/dist/settings/headless/providers.d.ts +17 -1
- package/dist/settings/headless/providers.js +19 -1
- package/dist/settings/headless/search.d.ts +1 -1
- package/dist/settings/rows.d.ts +1 -1
- package/dist/settings/rows.js +2 -2
- package/dist/settings/sections/appearance/AppearanceSection.js +2 -2
- package/dist/settings/sections/claude-import/ClaudeImportDialog.js +1 -1
- package/dist/settings/sections/editor/EditorSection.js +1 -1
- package/dist/settings/sections/environments/EnvironmentsSection.js +1 -1
- package/dist/settings/sections/extensions/ChangeOriginDialog.js +1 -1
- package/dist/settings/sections/extensions/CreateExtensionDialog.js +1 -1
- package/dist/settings/sections/extensions/ExtensionsSection.js +1 -1
- package/dist/settings/sections/host-catalog/HostCatalogSection.js +1 -1
- package/dist/settings/sections/instructions/InstructionsSection.js +1 -1
- package/dist/settings/sections/mcp/McpSection.js +1 -1
- package/dist/settings/sections/mcp/McpServerRow.js +1 -1
- package/dist/settings/sections/models/ModelRows.js +1 -1
- package/dist/settings/sections/providers/ProvidersSection.js +209 -201
- package/dist/settings/sections/providers/ServerProvidersSection.js +1 -1
- package/dist/settings/sections/shared/catalog.js +1 -1
- package/dist/settings/sections/skills/SkillsSection.js +1 -1
- package/dist/styles.css +59 -1
- package/dist/terminal/HostTerminalSurface.js +2 -2
- package/dist/workspace/PanelContentView.d.ts +22 -4
- package/dist/workspace/PanelContentView.js +112 -16
- package/dist/workspace/PanelGlyph.d.ts +21 -0
- package/dist/workspace/PanelGlyph.js +33 -0
- package/dist/workspace/PanelHome.js +5 -3
- package/dist/workspace/PanelStrip.js +11 -4
- package/dist/workspace/PanelToolbar.d.ts +32 -0
- package/dist/workspace/PanelToolbar.js +194 -0
- package/dist/workspace/WorkspaceProvider.d.ts +3 -1
- package/dist/workspace/WorkspaceProvider.js +6 -2
- package/dist/workspace/extension-tabs.js +5 -2
- package/dist/workspace/headless/host.d.ts +4 -0
- package/dist/workspace/headless/index.d.ts +3 -1
- package/dist/workspace/headless/index.js +4 -2
- package/dist/workspace/headless/labels.d.ts +10 -0
- package/dist/workspace/headless/labels.js +11 -1
- package/dist/workspace/headless/pages.d.ts +15 -0
- package/dist/workspace/headless/pages.js +49 -0
- package/dist/workspace/headless/tab-mark.d.ts +14 -0
- package/dist/workspace/headless/tab-mark.js +27 -0
- package/dist/workspace/headless/view.d.ts +7 -1
- package/dist/workspace/index.d.ts +3 -2
- package/dist/workspace/index.js +3 -2
- package/dist/workspace/testing/index.js +6 -0
- package/dist/workspace/workspace-key-commands.js +3 -3
- package/package.json +2 -2
- package/src/styles/motion.css +13 -0
- package/src/styles/onboarding.css +0 -1
- package/src/styles/primitives.css +10 -0
- package/src/styles/workspace.css +17 -0
- package/dist/session/TodosPanel.d.ts +0 -19
- package/dist/session/TodosPanel.js +0 -251
- package/dist/session/headless/todos.d.ts +0 -22
- package/dist/session/headless/todos.js +0 -38
|
@@ -1,46 +1,23 @@
|
|
|
1
1
|
import { createArtifactsState, groupArtifactRows } from "./artifacts.js";
|
|
2
2
|
import { createQueueState } from "./queue.js";
|
|
3
3
|
import { createRunningWorkState } from "./running-work.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createSchedulesState } from "./schedules.js";
|
|
5
|
+
import { createPlansState } from "./plans.js";
|
|
6
|
+
import { createSecretsState } from "./secrets.js";
|
|
7
|
+
import { createFixtureScheduleBackend } from "./fixture-schedules.js";
|
|
8
|
+
import { createFixturePlansBackend } from "./fixture-plans.js";
|
|
9
|
+
import { isSecretName } from "zidane/chat/pure";
|
|
5
10
|
//#region src/session/headless/fixture-host.ts
|
|
6
11
|
/**
|
|
7
|
-
* Counts todo items by status.
|
|
8
|
-
*
|
|
9
|
-
* @param items - The todo list.
|
|
10
|
-
*/
|
|
11
|
-
function tallyTodos(items) {
|
|
12
|
-
const tally = {
|
|
13
|
-
pending: 0,
|
|
14
|
-
in_progress: 0,
|
|
15
|
-
completed: 0,
|
|
16
|
-
cancelled: 0
|
|
17
|
-
};
|
|
18
|
-
for (const item of items) tally[item.status]++;
|
|
19
|
-
return tally;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* A todo snapshot of a list, as a backend broadcasts it.
|
|
23
|
-
*
|
|
24
|
-
* @param items - The live list.
|
|
25
|
-
* @param archive - The last non-empty list (shown read-only when the live one is empty).
|
|
26
|
-
*/
|
|
27
|
-
function todosSnapshotOf(items, archive = []) {
|
|
28
|
-
return {
|
|
29
|
-
runId: "fixture",
|
|
30
|
-
todos: [...items],
|
|
31
|
-
archive: [...archive],
|
|
32
|
-
inProgress: items.find((t) => t.status === "in_progress") ?? null,
|
|
33
|
-
tally: tallyTodos(items)
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
12
|
* Creates a {@link FixtureSessionHost} over the fixture's data.
|
|
38
13
|
*
|
|
39
14
|
* @param fixture - The data per session.
|
|
40
15
|
*/
|
|
41
16
|
function createFixtureSessionHost(fixture = {}) {
|
|
42
|
-
const
|
|
43
|
-
|
|
17
|
+
const planBackend = createFixturePlansBackend({
|
|
18
|
+
...fixture.plans ? { plans: fixture.plans } : {},
|
|
19
|
+
...fixture.todos ? { todos: fixture.todos } : {}
|
|
20
|
+
});
|
|
44
21
|
const queues = new Map(Object.entries(fixture.queue ?? {}).map(([id, entries]) => [id, [...entries]]));
|
|
45
22
|
const artifacts = new Map(Object.entries(fixture.artifacts ?? {}).map(([id, rows]) => [id, [...rows]]));
|
|
46
23
|
const work = new Map(Object.entries(fixture.work ?? {}).map(([id, list]) => [id, [...list]]));
|
|
@@ -94,10 +71,23 @@ function createFixtureSessionHost(fixture = {}) {
|
|
|
94
71
|
};
|
|
95
72
|
const sides = fixture.sides ? new Map(Object.entries(fixture.sides)) : null;
|
|
96
73
|
let sideSeq = 0;
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
74
|
+
const secretLists = new Map(Object.entries(fixture.secrets ?? {}).map(([id, list]) => [id, [...list]]));
|
|
75
|
+
const secretsState = createSecretsState(async (sessionId) => secretLists.get(sessionId) ?? []);
|
|
76
|
+
const writeSecrets = (sessionId, entries) => {
|
|
77
|
+
secretLists.set(sessionId, [...entries]);
|
|
78
|
+
secretsState.wakeAll();
|
|
100
79
|
};
|
|
80
|
+
const secretsSaveError = fixture.secretsSaveError;
|
|
81
|
+
const scheduleBackend = createFixtureScheduleBackend({
|
|
82
|
+
...fixture.schedules ? { schedules: fixture.schedules } : {},
|
|
83
|
+
...fixture.scheduleFires ? { fires: fixture.scheduleFires } : {},
|
|
84
|
+
...fixture.schedulesEnabled === false ? { enabled: false } : {}
|
|
85
|
+
});
|
|
86
|
+
const schedulesState = createSchedulesState({
|
|
87
|
+
status: async () => scheduleBackend.status(),
|
|
88
|
+
list: async () => scheduleBackend.list()
|
|
89
|
+
});
|
|
90
|
+
scheduleBackend.subscribe(() => schedulesState.wake());
|
|
101
91
|
const items = (sessionId) => (queues.get(sessionId) ?? []).map((e) => ({
|
|
102
92
|
id: e.id,
|
|
103
93
|
text: e.text,
|
|
@@ -106,13 +96,11 @@ function createFixtureSessionHost(fixture = {}) {
|
|
|
106
96
|
...a.name ? { name: a.name } : {}
|
|
107
97
|
})) } : {}
|
|
108
98
|
}));
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
todos.push(sessionId, snapshotOf(sessionId));
|
|
115
|
-
};
|
|
99
|
+
const plans = createPlansState(async (sessionId) => planBackend.snapshot(sessionId));
|
|
100
|
+
planBackend.subscribe((sessionId) => {
|
|
101
|
+
const snap = planBackend.snapshot(sessionId);
|
|
102
|
+
if (snap) plans.push(sessionId, snap);
|
|
103
|
+
});
|
|
116
104
|
const writeQueue = (sessionId, entries) => {
|
|
117
105
|
queues.set(sessionId, entries);
|
|
118
106
|
queue.replace(sessionId, items(sessionId));
|
|
@@ -132,12 +120,14 @@ function createFixtureSessionHost(fixture = {}) {
|
|
|
132
120
|
});
|
|
133
121
|
const artifactsState = createArtifactsState(async (sessionId) => groupArtifactRows(artifacts.get(sessionId) ?? [], fixture.kindLabel));
|
|
134
122
|
return {
|
|
135
|
-
|
|
136
|
-
snapshot:
|
|
137
|
-
refresh:
|
|
138
|
-
async
|
|
139
|
-
|
|
140
|
-
|
|
123
|
+
plans: {
|
|
124
|
+
snapshot: plans.snapshot,
|
|
125
|
+
refresh: plans.refresh,
|
|
126
|
+
async edit(sessionId, planId, op) {
|
|
127
|
+
planBackend.edit(sessionId, planId, op);
|
|
128
|
+
},
|
|
129
|
+
async control(sessionId, planId, action) {
|
|
130
|
+
planBackend.control(sessionId, planId, action);
|
|
141
131
|
},
|
|
142
132
|
async retry() {}
|
|
143
133
|
},
|
|
@@ -270,9 +260,49 @@ function createFixtureSessionHost(fixture = {}) {
|
|
|
270
260
|
for (const [parentId, id] of sides) if (id === sideId) sides.delete(parentId);
|
|
271
261
|
}
|
|
272
262
|
} } : {},
|
|
263
|
+
...fixture.secrets ? { secrets: {
|
|
264
|
+
entries: secretsState.entries,
|
|
265
|
+
refresh: secretsState.refresh,
|
|
266
|
+
async save(sessionId, input) {
|
|
267
|
+
if (secretsSaveError) throw new Error(secretsSaveError);
|
|
268
|
+
if (!isSecretName(input.name)) throw new Error(`Invalid secret name: ${input.name}`);
|
|
269
|
+
const createdAt = Date.now();
|
|
270
|
+
const entry = {
|
|
271
|
+
name: input.name,
|
|
272
|
+
createdAt,
|
|
273
|
+
expiresAt: fixtureSecretExpiry(input.ttl, createdAt),
|
|
274
|
+
sessionId
|
|
275
|
+
};
|
|
276
|
+
writeSecrets(sessionId, [...(secretLists.get(sessionId) ?? []).filter((e) => e.name !== input.name), entry]);
|
|
277
|
+
return entry;
|
|
278
|
+
},
|
|
279
|
+
async revoke(sessionId, name) {
|
|
280
|
+
const before = secretLists.get(sessionId) ?? [];
|
|
281
|
+
const after = before.filter((e) => e.name !== name);
|
|
282
|
+
writeSecrets(sessionId, after);
|
|
283
|
+
return after.length < before.length;
|
|
284
|
+
}
|
|
285
|
+
} } : {},
|
|
286
|
+
...fixture.schedules ? { schedules: {
|
|
287
|
+
status: schedulesState.status,
|
|
288
|
+
all: schedulesState.all,
|
|
289
|
+
forSession: schedulesState.forSession,
|
|
290
|
+
refresh: schedulesState.refresh,
|
|
291
|
+
ensure: schedulesState.ensure,
|
|
292
|
+
create: async (input) => scheduleBackend.create(input),
|
|
293
|
+
update: async (id, patch) => scheduleBackend.update(id, patch),
|
|
294
|
+
remove: async (id) => scheduleBackend.remove(id),
|
|
295
|
+
runNow: async (id) => scheduleBackend.runNow(id),
|
|
296
|
+
fires: async (id, limit) => scheduleBackend.fires(id, limit)
|
|
297
|
+
} } : {},
|
|
273
298
|
control: {
|
|
299
|
+
setSchedules: (list) => scheduleBackend.replace(list),
|
|
300
|
+
setSchedulesEnabled: (enabled) => scheduleBackend.setEnabled(enabled),
|
|
301
|
+
setSecrets: writeSecrets,
|
|
274
302
|
setWork: writeWork,
|
|
275
|
-
|
|
303
|
+
writeTodos: planBackend.writeTodos,
|
|
304
|
+
endRun: planBackend.endRun,
|
|
305
|
+
setPlans: planBackend.setPlans,
|
|
276
306
|
setQueue: (sessionId, entries) => writeQueue(sessionId, [...entries]),
|
|
277
307
|
recordArtifact(sessionId, row) {
|
|
278
308
|
artifacts.set(sessionId, [row, ...artifacts.get(sessionId) ?? []]);
|
|
@@ -281,5 +311,10 @@ function createFixtureSessionHost(fixture = {}) {
|
|
|
281
311
|
}
|
|
282
312
|
};
|
|
283
313
|
}
|
|
314
|
+
const HOUR = 36e5;
|
|
315
|
+
/** When a secret saved at `now` with `ttl` expires, as the backend computes it. */
|
|
316
|
+
function fixtureSecretExpiry(ttl, now) {
|
|
317
|
+
return ttl === "never" ? null : now + (ttl === "1h" ? HOUR : ttl === "24h" ? 24 * HOUR : 168 * HOUR);
|
|
318
|
+
}
|
|
284
319
|
//#endregion
|
|
285
|
-
export { createFixtureSessionHost
|
|
320
|
+
export { createFixtureSessionHost };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PlanControlAction } from "./host.js";
|
|
2
|
+
import { TodoTallyCounts } from "./plans.js";
|
|
3
|
+
import { Plan, PlanEditOp, PlanView, PlansSnapshot, TodoItem, TodoStatus } from "zidane/chat";
|
|
4
|
+
//#region src/session/headless/fixture-plans.d.ts
|
|
5
|
+
/** What a fixture plans backend starts with, per session id. */
|
|
6
|
+
interface FixturePlansSeed {
|
|
7
|
+
/** Each session's plans, oldest first (`possiblyInterrupted` shows an applying plan with no live run). */
|
|
8
|
+
plans?: Readonly<Record<string, readonly PlanView[]>>;
|
|
9
|
+
/** Each session's task list as the agent wrote it (`todowrite`), applied over `plans`: an applying plan. */
|
|
10
|
+
todos?: Readonly<Record<string, readonly TodoItem[]>>;
|
|
11
|
+
}
|
|
12
|
+
/** A plans backend in memory. */
|
|
13
|
+
interface FixturePlansBackend {
|
|
14
|
+
/** A session's snapshot, `null` for a session it does not hold. */
|
|
15
|
+
snapshot(sessionId: string): PlansSnapshot | null;
|
|
16
|
+
/** A user edit (`plans.edit`). Throws the gate's reason. */
|
|
17
|
+
edit(sessionId: string, planId: string, op: PlanEditOp): PlansSnapshot;
|
|
18
|
+
/** A stop or drop (`plans.control`). Throws the gate's reason. */
|
|
19
|
+
control(sessionId: string, planId: string, action: PlanControlAction): PlansSnapshot;
|
|
20
|
+
/** The agent's `todowrite`: updates the applying plan, starts one, or resumes a stopped one with the same step ids. */
|
|
21
|
+
writeTodos(sessionId: string, items: readonly TodoItem[]): void;
|
|
22
|
+
/** The session's run ended: the plan it applied stops. */
|
|
23
|
+
endRun(sessionId: string, outcome?: 'completed' | 'aborted' | 'error'): void;
|
|
24
|
+
/** Replaces a session's plans. */
|
|
25
|
+
setPlans(sessionId: string, plans: readonly PlanView[]): void;
|
|
26
|
+
/** Calls `listener` with a session's id after each change. Returns the unsubscribe. */
|
|
27
|
+
subscribe(listener: (sessionId: string) => void): () => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The snapshot of a session's plans, as the host broadcasts it (every run live: nothing reads as interrupted).
|
|
31
|
+
*
|
|
32
|
+
* @param plans - The plans, oldest first.
|
|
33
|
+
*/
|
|
34
|
+
declare function plansSnapshotOf(plans: readonly PlanView[]): PlansSnapshot;
|
|
35
|
+
/**
|
|
36
|
+
* A session-scope plan over a task list, for fixtures and tests.
|
|
37
|
+
*
|
|
38
|
+
* @param items - The steps, as todo items.
|
|
39
|
+
* @param plan - Fields over the defaults (an applying adhoc plan `plan-1`, untitled).
|
|
40
|
+
*/
|
|
41
|
+
declare function planOf(items: readonly TodoItem[], plan?: Partial<Plan>): Plan;
|
|
42
|
+
/**
|
|
43
|
+
* Counts todo items by status.
|
|
44
|
+
*
|
|
45
|
+
* @param items - The todo list.
|
|
46
|
+
*/
|
|
47
|
+
declare function tallyTodos(items: readonly {
|
|
48
|
+
status: TodoStatus;
|
|
49
|
+
}[]): TodoTallyCounts;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a {@link FixturePlansBackend}.
|
|
52
|
+
*
|
|
53
|
+
* @param seed - The plans and task lists it starts with.
|
|
54
|
+
*/
|
|
55
|
+
declare function createFixturePlansBackend(seed?: FixturePlansSeed): FixturePlansBackend;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { FixturePlansBackend, FixturePlansSeed, createFixturePlansBackend, planOf, plansSnapshotOf, tallyTodos };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { SESSION_SCOPE, activePlanSummary, applyPlanEvent } from "zidane/chat/pure";
|
|
2
|
+
//#region src/session/headless/fixture-plans.ts
|
|
3
|
+
const bagOf = (plans) => ({
|
|
4
|
+
v: 1,
|
|
5
|
+
plans: [...plans],
|
|
6
|
+
seen: []
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* The snapshot of a session's plans, as the host broadcasts it (every run live: nothing reads as interrupted).
|
|
10
|
+
*
|
|
11
|
+
* @param plans - The plans, oldest first.
|
|
12
|
+
*/
|
|
13
|
+
function plansSnapshotOf(plans) {
|
|
14
|
+
return {
|
|
15
|
+
plans: [...plans],
|
|
16
|
+
active: activePlanSummary(bagOf(plans))
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A session-scope plan over a task list, for fixtures and tests.
|
|
21
|
+
*
|
|
22
|
+
* @param items - The steps, as todo items.
|
|
23
|
+
* @param plan - Fields over the defaults (an applying adhoc plan `plan-1`, untitled).
|
|
24
|
+
*/
|
|
25
|
+
function planOf(items, plan = {}) {
|
|
26
|
+
return {
|
|
27
|
+
id: "plan-1",
|
|
28
|
+
scope: SESSION_SCOPE,
|
|
29
|
+
kind: "adhoc",
|
|
30
|
+
state: "applying",
|
|
31
|
+
title: "",
|
|
32
|
+
revision: 1,
|
|
33
|
+
createdAt: 1,
|
|
34
|
+
updatedAt: 1,
|
|
35
|
+
steps: items.map((item) => ({
|
|
36
|
+
id: item.id,
|
|
37
|
+
title: item.content,
|
|
38
|
+
status: item.status,
|
|
39
|
+
updatedAt: 1
|
|
40
|
+
})),
|
|
41
|
+
...plan
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Counts todo items by status.
|
|
46
|
+
*
|
|
47
|
+
* @param items - The todo list.
|
|
48
|
+
*/
|
|
49
|
+
function tallyTodos(items) {
|
|
50
|
+
const tally = {
|
|
51
|
+
pending: 0,
|
|
52
|
+
in_progress: 0,
|
|
53
|
+
completed: 0,
|
|
54
|
+
cancelled: 0
|
|
55
|
+
};
|
|
56
|
+
for (const item of items) tally[item.status]++;
|
|
57
|
+
return tally;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a {@link FixturePlansBackend}.
|
|
61
|
+
*
|
|
62
|
+
* @param seed - The plans and task lists it starts with.
|
|
63
|
+
*/
|
|
64
|
+
function createFixturePlansBackend(seed = {}) {
|
|
65
|
+
const bags = new Map(Object.entries(seed.plans ?? {}).map(([id, plans]) => [id, bagOf(plans)]));
|
|
66
|
+
const runs = /* @__PURE__ */ new Map();
|
|
67
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
68
|
+
let seq = 0;
|
|
69
|
+
const runId = (sessionId) => `fixture-run-${sessionId}-${runs.get(sessionId) ?? 0}`;
|
|
70
|
+
const apply = (sessionId, event) => {
|
|
71
|
+
const result = applyPlanEvent(bags.get(sessionId) ?? bagOf([]), event);
|
|
72
|
+
if (result.error) throw new Error(result.error);
|
|
73
|
+
if (result.changed || !bags.has(sessionId)) {
|
|
74
|
+
bags.set(sessionId, result.bag);
|
|
75
|
+
for (const listener of listeners) listener(sessionId);
|
|
76
|
+
}
|
|
77
|
+
return result.bag;
|
|
78
|
+
};
|
|
79
|
+
const writeTodos = (sessionId, items) => {
|
|
80
|
+
apply(sessionId, {
|
|
81
|
+
type: "write",
|
|
82
|
+
id: `write:${++seq}`,
|
|
83
|
+
at: Date.now(),
|
|
84
|
+
scope: SESSION_SCOPE,
|
|
85
|
+
runId: runId(sessionId),
|
|
86
|
+
items: items.map((item) => ({
|
|
87
|
+
id: item.id,
|
|
88
|
+
content: item.content,
|
|
89
|
+
status: item.status
|
|
90
|
+
}))
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
for (const [sessionId, items] of Object.entries(seed.todos ?? {})) writeTodos(sessionId, items);
|
|
94
|
+
return {
|
|
95
|
+
snapshot(sessionId) {
|
|
96
|
+
const bag = bags.get(sessionId);
|
|
97
|
+
return bag ? plansSnapshotOf(bag.plans) : null;
|
|
98
|
+
},
|
|
99
|
+
edit(sessionId, planId, op) {
|
|
100
|
+
return plansSnapshotOf(apply(sessionId, {
|
|
101
|
+
type: "edit",
|
|
102
|
+
id: `edit:${++seq}`,
|
|
103
|
+
at: Date.now(),
|
|
104
|
+
planId,
|
|
105
|
+
op
|
|
106
|
+
}).plans);
|
|
107
|
+
},
|
|
108
|
+
control(sessionId, planId, action) {
|
|
109
|
+
const event = {
|
|
110
|
+
type: "control",
|
|
111
|
+
id: `control:${++seq}`,
|
|
112
|
+
at: Date.now(),
|
|
113
|
+
planId,
|
|
114
|
+
action
|
|
115
|
+
};
|
|
116
|
+
return plansSnapshotOf(apply(sessionId, event).plans);
|
|
117
|
+
},
|
|
118
|
+
writeTodos,
|
|
119
|
+
endRun(sessionId, outcome = "completed") {
|
|
120
|
+
const id = runId(sessionId);
|
|
121
|
+
runs.set(sessionId, (runs.get(sessionId) ?? 0) + 1);
|
|
122
|
+
apply(sessionId, {
|
|
123
|
+
type: "runEnd",
|
|
124
|
+
id: `runEnd:${id}`,
|
|
125
|
+
at: Date.now(),
|
|
126
|
+
runId: id,
|
|
127
|
+
outcome
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
setPlans(sessionId, plans) {
|
|
131
|
+
bags.set(sessionId, bagOf(plans));
|
|
132
|
+
for (const listener of listeners) listener(sessionId);
|
|
133
|
+
},
|
|
134
|
+
subscribe(listener) {
|
|
135
|
+
listeners.add(listener);
|
|
136
|
+
return () => listeners.delete(listener);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { createFixturePlansBackend, planOf, plansSnapshotOf, tallyTodos };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SchedulesStatus } from "./host.js";
|
|
2
|
+
import { Schedule, ScheduleFire } from "zidane/chat/pure";
|
|
3
|
+
//#region src/session/headless/fixture-schedules.d.ts
|
|
4
|
+
/** A scripted reminders backend. */
|
|
5
|
+
interface FixtureScheduleBackend {
|
|
6
|
+
/** `schedules.status`. */
|
|
7
|
+
status(): SchedulesStatus;
|
|
8
|
+
/** `schedules.list`: every schedule, or those whose prompt goes to `sessionId`. */
|
|
9
|
+
list(filter?: {
|
|
10
|
+
sessionId?: string;
|
|
11
|
+
}): Schedule[];
|
|
12
|
+
/** `schedules.create`, as the person (`createdBy: user`). */
|
|
13
|
+
create(input: unknown): Schedule;
|
|
14
|
+
/** `schedules.update`. */
|
|
15
|
+
update(id: string, patch: unknown): Schedule;
|
|
16
|
+
/** `schedules.delete`: `false` when it was gone. */
|
|
17
|
+
remove(id: string): boolean;
|
|
18
|
+
/** `schedules.runNow`: records a fire now; the next time does not move. */
|
|
19
|
+
runNow(id: string): ScheduleFire;
|
|
20
|
+
/** `schedules.fires`: newest first. */
|
|
21
|
+
fires(id: string, limit?: number): ScheduleFire[];
|
|
22
|
+
/** Replaces the stored schedules from outside (the agent set one, one fired). */
|
|
23
|
+
replace(list: readonly Schedule[]): void;
|
|
24
|
+
/** Turns the setting on or off. */
|
|
25
|
+
setEnabled(enabled: boolean): void;
|
|
26
|
+
/** Hears every change (`schedules:changed`). Returns the unsubscribe. */
|
|
27
|
+
subscribe(onChange: (scheduleId?: string) => void): () => void;
|
|
28
|
+
}
|
|
29
|
+
/** What {@link createFixtureScheduleBackend} starts with. */
|
|
30
|
+
interface FixtureScheduleOptions {
|
|
31
|
+
/** The stored schedules, oldest first. */
|
|
32
|
+
schedules?: readonly Schedule[];
|
|
33
|
+
/** Each schedule's fires, newest first. */
|
|
34
|
+
fires?: Readonly<Record<string, readonly ScheduleFire[]>>;
|
|
35
|
+
/** `Settings.schedules`. @default true */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/** The clock. @default Date.now */
|
|
38
|
+
now?: () => number;
|
|
39
|
+
}
|
|
40
|
+
/** The message zidane's backend answers while the setting is off. */
|
|
41
|
+
declare const SCHEDULES_OFF_MESSAGE = "Reminders are off. Turn them on in Settings > Sessions > Reminders.";
|
|
42
|
+
/**
|
|
43
|
+
* Creates a {@link FixtureScheduleBackend}.
|
|
44
|
+
*
|
|
45
|
+
* @param options - The starting schedules, fires, setting and clock.
|
|
46
|
+
*/
|
|
47
|
+
declare function createFixtureScheduleBackend(options?: FixtureScheduleOptions): FixtureScheduleBackend;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { FixtureScheduleBackend, FixtureScheduleOptions, SCHEDULES_OFF_MESSAGE, createFixtureScheduleBackend };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { defaultCatchUp, nextFireAfter, scheduleTargetSession, validateScheduleInput, validateSchedulePatch } from "zidane/chat/pure";
|
|
2
|
+
//#region src/session/headless/fixture-schedules.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reminders (zidane's schedules) held in memory, as zidane's backend keeps
|
|
5
|
+
* them: the fixture session host's `schedules`, and the scripted backend an
|
|
6
|
+
* app's own `SessionHost` test serves its transport from. Methods answer
|
|
7
|
+
* like the wire methods (`schedules.status`, `.list`, `.create`, `.update`,
|
|
8
|
+
* `.delete`, `.runNow`, `.fires`): they throw the backend's message while
|
|
9
|
+
* the setting is off, and zidane's `ScheduleInputError` for a bad input.
|
|
10
|
+
*/
|
|
11
|
+
/** The message zidane's backend answers while the setting is off. */
|
|
12
|
+
const SCHEDULES_OFF_MESSAGE = "Reminders are off. Turn them on in Settings > Sessions > Reminders.";
|
|
13
|
+
/**
|
|
14
|
+
* Creates a {@link FixtureScheduleBackend}.
|
|
15
|
+
*
|
|
16
|
+
* @param options - The starting schedules, fires, setting and clock.
|
|
17
|
+
*/
|
|
18
|
+
function createFixtureScheduleBackend(options = {}) {
|
|
19
|
+
const now = options.now ?? Date.now;
|
|
20
|
+
let list = [...options.schedules ?? []];
|
|
21
|
+
const fireLists = new Map(Object.entries(options.fires ?? {}).map(([id, fires]) => [id, [...fires]]));
|
|
22
|
+
let enabled = options.enabled !== false;
|
|
23
|
+
let seq = 0;
|
|
24
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
25
|
+
const changed = (scheduleId) => {
|
|
26
|
+
for (const l of [...listeners]) l(scheduleId);
|
|
27
|
+
};
|
|
28
|
+
const on = () => {
|
|
29
|
+
if (!enabled) throw new Error(SCHEDULES_OFF_MESSAGE);
|
|
30
|
+
};
|
|
31
|
+
const find = (id) => {
|
|
32
|
+
on();
|
|
33
|
+
const found = list.find((s) => s.id === id);
|
|
34
|
+
if (!found) throw new Error(`No schedule "${id}".`);
|
|
35
|
+
return found;
|
|
36
|
+
};
|
|
37
|
+
const write = (next, scheduleId) => {
|
|
38
|
+
list = next;
|
|
39
|
+
changed(scheduleId);
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
status: () => ({
|
|
43
|
+
enabled,
|
|
44
|
+
supported: true
|
|
45
|
+
}),
|
|
46
|
+
list(filter) {
|
|
47
|
+
on();
|
|
48
|
+
const sessionId = filter?.sessionId;
|
|
49
|
+
return sessionId === void 0 ? [...list] : list.filter((s) => scheduleTargetSession(s.action) === sessionId);
|
|
50
|
+
},
|
|
51
|
+
create(raw) {
|
|
52
|
+
on();
|
|
53
|
+
const at = now();
|
|
54
|
+
const input = validateScheduleInput(raw, at);
|
|
55
|
+
const schedule = {
|
|
56
|
+
id: `fixture-sch${++seq}`,
|
|
57
|
+
...input.name ? { name: input.name } : {},
|
|
58
|
+
trigger: input.trigger,
|
|
59
|
+
action: input.action,
|
|
60
|
+
catchUp: input.catchUp ?? defaultCatchUp(input.trigger),
|
|
61
|
+
enabled: input.enabled !== false,
|
|
62
|
+
createdBy: { kind: "user" },
|
|
63
|
+
createdAt: at,
|
|
64
|
+
updatedAt: at,
|
|
65
|
+
nextFireAt: input.enabled === false ? null : nextFireAfter(input.trigger, at)
|
|
66
|
+
};
|
|
67
|
+
write([...list, schedule], schedule.id);
|
|
68
|
+
return schedule;
|
|
69
|
+
},
|
|
70
|
+
update(id, raw) {
|
|
71
|
+
const found = find(id);
|
|
72
|
+
const at = now();
|
|
73
|
+
const { name, expiresAt, ...rest } = validateSchedulePatch(raw, at);
|
|
74
|
+
const merged = {
|
|
75
|
+
...found,
|
|
76
|
+
...rest,
|
|
77
|
+
updatedAt: at
|
|
78
|
+
};
|
|
79
|
+
if (name === null) delete merged.name;
|
|
80
|
+
else if (name !== void 0) merged.name = name;
|
|
81
|
+
if (expiresAt === null) delete merged.expiresAt;
|
|
82
|
+
if (rest.enabled === true) delete merged.disabledReason;
|
|
83
|
+
merged.nextFireAt = merged.enabled ? nextFireAfter(merged.trigger, at) : null;
|
|
84
|
+
write(list.map((s) => s.id === id ? merged : s), id);
|
|
85
|
+
return merged;
|
|
86
|
+
},
|
|
87
|
+
remove(id) {
|
|
88
|
+
on();
|
|
89
|
+
if (!list.some((s) => s.id === id)) return false;
|
|
90
|
+
fireLists.delete(id);
|
|
91
|
+
write(list.filter((s) => s.id !== id), id);
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
runNow(id) {
|
|
95
|
+
const found = find(id);
|
|
96
|
+
const at = now();
|
|
97
|
+
const target = scheduleTargetSession(found.action);
|
|
98
|
+
const fire = {
|
|
99
|
+
id: `fixture-fire${++seq}`,
|
|
100
|
+
scheduleId: id,
|
|
101
|
+
dueAt: at,
|
|
102
|
+
firedAt: at,
|
|
103
|
+
outcome: found.action.kind === "extension" ? "handled" : "sent",
|
|
104
|
+
...target ? { sessionId: target } : {}
|
|
105
|
+
};
|
|
106
|
+
fireLists.set(id, [fire, ...fireLists.get(id) ?? []]);
|
|
107
|
+
write(list.map((s) => s.id === id ? {
|
|
108
|
+
...s,
|
|
109
|
+
lastFire: fire
|
|
110
|
+
} : s), id);
|
|
111
|
+
return fire;
|
|
112
|
+
},
|
|
113
|
+
fires(id, limit = 20) {
|
|
114
|
+
find(id);
|
|
115
|
+
return (fireLists.get(id) ?? []).slice(0, limit);
|
|
116
|
+
},
|
|
117
|
+
replace(next) {
|
|
118
|
+
write([...next]);
|
|
119
|
+
},
|
|
120
|
+
setEnabled(next) {
|
|
121
|
+
enabled = next;
|
|
122
|
+
changed();
|
|
123
|
+
},
|
|
124
|
+
subscribe(onChange) {
|
|
125
|
+
listeners.add(onChange);
|
|
126
|
+
return () => {
|
|
127
|
+
listeners.delete(onChange);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { SCHEDULES_OFF_MESSAGE, createFixtureScheduleBackend };
|
|
@@ -8,6 +8,10 @@ interface HomeBlockSource {
|
|
|
8
8
|
label: string;
|
|
9
9
|
/** The extension that registered it. */
|
|
10
10
|
owner: string;
|
|
11
|
+
/** That extension's display name. */
|
|
12
|
+
ownerName?: string;
|
|
13
|
+
/** That extension's own icon: its mark stands in for a panel without one. */
|
|
14
|
+
ownerIcon?: PanelIcon;
|
|
11
15
|
/** Its icon. */
|
|
12
16
|
icon?: PanelIcon;
|
|
13
17
|
/** Where it may show; only panels that list `'home'` become blocks. */
|
|
@@ -23,6 +27,10 @@ interface HomeBlock {
|
|
|
23
27
|
label: string;
|
|
24
28
|
/** The extension that registered it. */
|
|
25
29
|
owner: string;
|
|
30
|
+
/** That extension's display name. */
|
|
31
|
+
ownerName?: string;
|
|
32
|
+
/** That extension's own icon: its mark stands in for a block without one. */
|
|
33
|
+
ownerIcon?: PanelIcon;
|
|
26
34
|
/** Its icon. */
|
|
27
35
|
icon?: PanelIcon;
|
|
28
36
|
/** Shares a row with another half block. */
|
|
@@ -124,9 +132,9 @@ declare function showHomeBlock(prefs: HomePrefs, blocks: readonly HomeBlock[], i
|
|
|
124
132
|
* @param prefs - The arrangement.
|
|
125
133
|
* @param id - The block.
|
|
126
134
|
* @param control - The control id.
|
|
127
|
-
* @param value - The picked option.
|
|
135
|
+
* @param value - The picked option, or the picked options of a `multi` control.
|
|
128
136
|
*/
|
|
129
|
-
declare function setHomeBlockView(prefs: HomePrefs, id: string, control: string, value: string): HomePrefs;
|
|
137
|
+
declare function setHomeBlockView(prefs: HomePrefs, id: string, control: string, value: string | readonly string[]): HomePrefs;
|
|
130
138
|
/**
|
|
131
139
|
* Every project's arrangement from its stored form; `undefined` for anything unrecognised.
|
|
132
140
|
*
|
|
@@ -21,6 +21,8 @@ function homeBlocksOf(panels) {
|
|
|
21
21
|
id: p.id,
|
|
22
22
|
label: p.label,
|
|
23
23
|
owner: p.owner,
|
|
24
|
+
...p.ownerName ? { ownerName: p.ownerName } : {},
|
|
25
|
+
...p.ownerIcon ? { ownerIcon: p.ownerIcon } : {},
|
|
24
26
|
...p.icon ? { icon: p.icon } : {},
|
|
25
27
|
width: p.home?.width === "half" ? "half" : "full"
|
|
26
28
|
}));
|
|
@@ -173,10 +175,11 @@ function showHomeBlock(prefs, blocks, id, defaults) {
|
|
|
173
175
|
* @param prefs - The arrangement.
|
|
174
176
|
* @param id - The block.
|
|
175
177
|
* @param control - The control id.
|
|
176
|
-
* @param value - The picked option.
|
|
178
|
+
* @param value - The picked option, or the picked options of a `multi` control.
|
|
177
179
|
*/
|
|
178
180
|
function setHomeBlockView(prefs, id, control, value) {
|
|
179
|
-
|
|
181
|
+
const was = prefs.views[id]?.[control];
|
|
182
|
+
if (was === value || Array.isArray(was) && Array.isArray(value) && sameList(was, value)) return prefs;
|
|
180
183
|
return {
|
|
181
184
|
...prefs,
|
|
182
185
|
views: {
|
|
@@ -189,10 +192,12 @@ function setHomeBlockView(prefs, id, control, value) {
|
|
|
189
192
|
};
|
|
190
193
|
}
|
|
191
194
|
const strings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && x.length > 0 && x.length <= 512) : [];
|
|
195
|
+
const sameList = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
192
196
|
function parseView(v) {
|
|
193
197
|
if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
|
|
194
198
|
const out = {};
|
|
195
199
|
for (const [k, value] of Object.entries(v)) if (typeof value === "string") out[k] = value;
|
|
200
|
+
else if (Array.isArray(value)) out[k] = value.filter((x) => typeof x === "string");
|
|
196
201
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
197
202
|
}
|
|
198
203
|
/**
|