@design.estate/dees-catalog 9.0.0 → 9.1.0
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_bundle/bundle.js +2636 -2150
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.d.ts +7 -2
- package/dist_ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.js +56 -5
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +62 -18
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +4 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +7 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +69 -29
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +6 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.d.ts +2 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.js +419 -83
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +247 -118
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +2 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/renderers.js +2 -4
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +15 -4
- package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +2 -1
- package/dist_ts_web/elements/00group-harness/toolregistry.js +1 -1
- package/dist_ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.d.ts +1 -1
- package/dist_ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js +30 -12
- package/dist_ts_web/elements/00theme.js +3 -1
- package/package.json +1 -1
- package/readme.md +19 -3
- package/readme.plan.md +97 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts +48 -4
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +62 -17
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +6 -1
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +60 -27
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +341 -88
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +257 -125
- package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-tool-card/renderers.ts +1 -3
- package/ts_web/elements/00group-harness/interfaces.ts +16 -4
- package/ts_web/elements/00group-harness/toolregistry.ts +2 -1
- package/ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.ts +29 -15
- package/ts_web/elements/00theme.ts +2 -0
package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts
CHANGED
|
@@ -1,37 +1,63 @@
|
|
|
1
|
-
import { html } from '@design.estate/dees-element';
|
|
2
|
-
import {
|
|
1
|
+
import { DeesElement, css, customElement, directives, html, state } from '@design.estate/dees-element';
|
|
2
|
+
import { demoApp, demoOutput, demoStyles, showDemoResult } from '../../../demos/templates.js';
|
|
3
|
+
import { themeDefaultStyles } from '../../00theme.js';
|
|
4
|
+
import { harnessPillStyles } from '../harness.styles.js';
|
|
5
|
+
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
|
6
|
+
import '../dees-harness-question-card/dees-harness-question-card.js';
|
|
7
|
+
import '../dees-harness-permission-card/dees-harness-permission-card.js';
|
|
8
|
+
import type { DeesHarnessSessionList } from './dees-harness-session-list.js';
|
|
3
9
|
import type {
|
|
4
10
|
IHarnessResourceAssociation,
|
|
5
11
|
IHarnessResourceContextDetail,
|
|
6
12
|
IHarnessResourceEventDetail,
|
|
7
13
|
IHarnessResourceMeta,
|
|
8
14
|
IHarnessSessionGroup,
|
|
15
|
+
IHarnessSessionContextDetail,
|
|
9
16
|
IHarnessSessionMeta,
|
|
10
17
|
IHarnessSessionMoveDetail,
|
|
18
|
+
IHarnessQuestionResponseDetail,
|
|
19
|
+
IHarnessPermissionResponseDetail,
|
|
11
20
|
THarnessResourceAssociationRequestDetail,
|
|
12
21
|
} from '../interfaces.js';
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
type TDemoStage = 'idle' | 'starting' | 'working' | 'feedback' | 'approval' | 'finished' | 'error' | 'paused';
|
|
24
|
+
interface IDemoSession extends IHarnessSessionMeta {
|
|
25
|
+
stage: TDemoStage;
|
|
26
|
+
outcome?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const stages: Record<TDemoStage, { state: NonNullable<IHarnessSessionMeta['state']>; label: string; description: string }> = {
|
|
30
|
+
idle: { state: 'normal', label: 'Ready to start', description: 'The brief is ready. Start the session to prepare the workspace.' },
|
|
31
|
+
starting: { state: 'working', label: 'Starting', description: 'Opening the workspace and reading the project instructions.' },
|
|
32
|
+
working: { state: 'working', label: 'Working', description: 'The agent is inspecting the changes and running focused checks.' },
|
|
33
|
+
feedback: { state: 'attention', label: 'Waiting for feedback', description: 'Work is paused at a decision. Your answer will let the agent continue.' },
|
|
34
|
+
approval: { state: 'attention', label: 'Approval needed', description: 'The agent has prepared the next action and is waiting for permission.' },
|
|
35
|
+
finished: { state: 'finished', label: 'Completed', description: 'The changes and focused checks are complete. The result is ready to review.' },
|
|
36
|
+
error: { state: 'error', label: 'Run failed', description: 'The model endpoint rejected the request. Retry when the connection is ready.' },
|
|
37
|
+
paused: { state: 'normal', label: 'Paused', description: 'Permission was declined. The requested action did not run.' },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const demoSessions = (): IDemoSession[] => [
|
|
41
|
+
{ title: 'Fix sitemap cache key', harness: { id: 'codex', name: 'Codex' }, stage: 'approval' as const },
|
|
42
|
+
{ title: 'Investigate model rate limits', harness: { id: 'opencode', name: 'OpenCode' }, stage: 'working' as const },
|
|
43
|
+
{ title: 'Refactor workbench layout', harness: { id: 'flexharness', name: 'FlexHarness' }, stage: 'feedback' as const },
|
|
44
|
+
{ title: 'Add adaptive code diffs', harness: { id: 'codex', name: 'Codex' }, stage: 'finished' as const },
|
|
45
|
+
{ title: 'Prepare the catalog release', harness: { id: 'opencode', name: 'OpenCode' }, stage: 'error' as const },
|
|
46
|
+
{ title: 'Improve agent onboarding', harness: { id: 'flexharness', name: 'FlexHarness' }, stage: 'idle' as const },
|
|
47
|
+
{ title: 'Draft the migration guide', harness: { id: 'codex', name: 'Codex' }, stage: 'starting' as const },
|
|
48
|
+
].map((session, index) => ({
|
|
49
|
+
...session,
|
|
15
50
|
id: `session-${index}`,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'Release 3.91.0',
|
|
22
|
-
'Migrate agent chat to harness group',
|
|
23
|
-
][index % 6],
|
|
24
|
-
createdAt: Date.now() - (index + 2) * 3_600_000,
|
|
25
|
-
updatedAt: Date.now() - (index + 1) * 3_500_000,
|
|
26
|
-
messageCount: 4 + (index % 9),
|
|
27
|
-
preview: 'Latest: the regression test asserts the cache key stays stable across…',
|
|
28
|
-
state: (['normal', 'working', 'finished', 'attention', 'error'] as const)[index % 5],
|
|
29
|
-
working: index % 5 === 1,
|
|
51
|
+
updatedAt: Date.now() - (index + 1) * 60_000,
|
|
52
|
+
messageCount: 4 + index * 3,
|
|
53
|
+
state: stages[session.stage].state,
|
|
54
|
+
statusLabel: stages[session.stage].label,
|
|
55
|
+
preview: stages[session.stage].description,
|
|
30
56
|
}));
|
|
31
57
|
|
|
32
58
|
const demoGroups: IHarnessSessionGroup[] = [
|
|
33
|
-
{ id: 'active', name: '
|
|
34
|
-
{ id: 'review', name: '
|
|
59
|
+
{ id: 'active', name: 'Workspace', sessionIds: ['session-0', 'session-1', 'session-2', 'session-6'] },
|
|
60
|
+
{ id: 'review', name: 'Recent', sessionIds: ['session-3', 'session-5', 'session-4'] },
|
|
35
61
|
];
|
|
36
62
|
|
|
37
63
|
const demoResources: IHarnessResourceMeta[] = [
|
|
@@ -49,7 +75,7 @@ const demoResources: IHarnessResourceMeta[] = [
|
|
|
49
75
|
kind: 'file',
|
|
50
76
|
title: 'Release notes',
|
|
51
77
|
icon: 'lucide:FileText',
|
|
52
|
-
preview: '
|
|
78
|
+
preview: 'Catalog migration notes',
|
|
53
79
|
updatedAt: Date.now() - 180_000,
|
|
54
80
|
},
|
|
55
81
|
{
|
|
@@ -63,22 +89,15 @@ const demoResources: IHarnessResourceMeta[] = [
|
|
|
63
89
|
];
|
|
64
90
|
|
|
65
91
|
const demoResourceAssociations: IHarnessResourceAssociation[] = [
|
|
66
|
-
{ resourceId: 'terminal:build', sessionId: 'session-
|
|
67
|
-
{ resourceId: 'file:release-notes', sessionId: 'session-
|
|
92
|
+
{ resourceId: 'terminal:build', sessionId: 'session-4' },
|
|
93
|
+
{ resourceId: 'file:release-notes', sessionId: 'session-4' },
|
|
68
94
|
];
|
|
69
95
|
|
|
70
96
|
const demoResourceEligibleSessionIds = Object.fromEntries(
|
|
71
|
-
demoResources.map((resource) => [resource.id, ['session-0', 'session-1', 'session-2']]),
|
|
97
|
+
demoResources.map((resource) => [resource.id, ['session-0', 'session-1', 'session-2', 'session-4']]),
|
|
72
98
|
);
|
|
73
99
|
|
|
74
|
-
type TDemoSessionList =
|
|
75
|
-
groups: IHarnessSessionGroup[];
|
|
76
|
-
resources: IHarnessResourceMeta[];
|
|
77
|
-
resourceAssociations: IHarnessResourceAssociation[];
|
|
78
|
-
selectedResourceId: string;
|
|
79
|
-
selectedSessionId: string;
|
|
80
|
-
ungroupedSessionIds: string[];
|
|
81
|
-
};
|
|
100
|
+
type TDemoSessionList = DeesHarnessSessionList;
|
|
82
101
|
|
|
83
102
|
const handleSessionSelect = (event: CustomEvent<{ session: IHarnessSessionMeta }>): void => {
|
|
84
103
|
const list = event.currentTarget as TDemoSessionList;
|
|
@@ -87,7 +106,11 @@ const handleSessionSelect = (event: CustomEvent<{ session: IHarnessSessionMeta }
|
|
|
87
106
|
|
|
88
107
|
const handleSessionMove = (event: CustomEvent<IHarnessSessionMoveDetail>): void => {
|
|
89
108
|
const list = event.currentTarget as TDemoSessionList;
|
|
90
|
-
|
|
109
|
+
applySessionMove(list, event.detail);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const applySessionMove = (list: TDemoSessionList, detail: IHarnessSessionMoveDetail): void => {
|
|
113
|
+
const { sessionId, groupId, beforeSessionId } = detail;
|
|
91
114
|
const groups = list.groups.map((group) => ({
|
|
92
115
|
...group,
|
|
93
116
|
sessionIds: group.sessionIds.filter((id) => id !== sessionId),
|
|
@@ -103,6 +126,64 @@ const handleSessionMove = (event: CustomEvent<IHarnessSessionMoveDetail>): void
|
|
|
103
126
|
list.ungroupedSessionIds = ungroupedSessionIds;
|
|
104
127
|
};
|
|
105
128
|
|
|
129
|
+
const handleSessionOpen = (event: CustomEvent<{ session: IHarnessSessionMeta }>): void => {
|
|
130
|
+
const list = event.currentTarget as TDemoSessionList;
|
|
131
|
+
list.selectedSessionId = event.detail.session.id;
|
|
132
|
+
showDemoResult(list, `Opened “${event.detail.session.title}”.`);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handleSessionContext = async (
|
|
136
|
+
event: CustomEvent<IHarnessSessionContextDetail>,
|
|
137
|
+
onChange: (opened?: boolean) => void = () => {},
|
|
138
|
+
): Promise<void> => {
|
|
139
|
+
const list = event.currentTarget as TDemoSessionList;
|
|
140
|
+
const { session, originalEvent } = event.detail;
|
|
141
|
+
const invokingControl = originalEvent.composedPath().find(
|
|
142
|
+
(target): target is HTMLButtonElement => target instanceof HTMLButtonElement,
|
|
143
|
+
);
|
|
144
|
+
const currentGroup = list.groups.find((group) => group.sessionIds.includes(session.id));
|
|
145
|
+
// Capture dialog ownership before event dispatch ends: no await before opening.
|
|
146
|
+
const menu = await DeesContextmenu.openContextMenuWithOptions(originalEvent, [
|
|
147
|
+
{
|
|
148
|
+
name: 'Open conversation', iconName: 'lucide:MessageSquare',
|
|
149
|
+
action: async () => {
|
|
150
|
+
list.selectedSessionId = session.id;
|
|
151
|
+
onChange(true);
|
|
152
|
+
showDemoResult(list, `Opened “${session.title}”.`);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'Show details', iconName: 'lucide:Info',
|
|
157
|
+
action: async () => showDemoResult(list, `${session.title} · ${session.messageCount ?? 0} messages · ${session.state ?? 'normal'}`),
|
|
158
|
+
},
|
|
159
|
+
...list.groups.filter((group) => group.id !== currentGroup?.id).map((group) => ({
|
|
160
|
+
name: `Move to ${group.name}`, iconName: 'lucide:FolderInput',
|
|
161
|
+
action: async () => {
|
|
162
|
+
applySessionMove(list, { sessionId: session.id, groupId: group.id, index: group.sessionIds.length });
|
|
163
|
+
onChange();
|
|
164
|
+
showDemoResult(list, `Moved “${session.title}” to ${group.name}.`);
|
|
165
|
+
},
|
|
166
|
+
})),
|
|
167
|
+
...(currentGroup ? [{
|
|
168
|
+
name: 'Move to Ungrouped', iconName: 'lucide:FolderInput',
|
|
169
|
+
action: async () => {
|
|
170
|
+
applySessionMove(list, { sessionId: session.id, groupId: null, index: list.ungroupedSessionIds.length });
|
|
171
|
+
onChange();
|
|
172
|
+
showDemoResult(list, `Moved “${session.title}” to Ungrouped.`);
|
|
173
|
+
},
|
|
174
|
+
}] : []),
|
|
175
|
+
]);
|
|
176
|
+
menu?.registerGarbageFunction(async () => {
|
|
177
|
+
await list.updateComplete;
|
|
178
|
+
if (!list.isConnected) return;
|
|
179
|
+
// Moving between groups recreates the row; restore its equivalent control.
|
|
180
|
+
const control = invokingControl?.isConnected ? invokingControl : list.shadowRoot?.querySelector<HTMLButtonElement>(
|
|
181
|
+
`[data-session-id="${CSS.escape(session.id)}"] .${invokingControl?.classList.contains('itemPrimary') ? 'itemPrimary' : 'sessionActions'}`,
|
|
182
|
+
);
|
|
183
|
+
control?.focus({ preventScroll: true });
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
106
187
|
const handleResourceAssociation = (
|
|
107
188
|
event: CustomEvent<THarnessResourceAssociationRequestDetail>,
|
|
108
189
|
): void => {
|
|
@@ -123,9 +204,8 @@ const handleResourceContext = async (
|
|
|
123
204
|
event: CustomEvent<IHarnessResourceContextDetail>,
|
|
124
205
|
): Promise<void> => {
|
|
125
206
|
const list = event.currentTarget as TDemoSessionList;
|
|
126
|
-
const { DeesContextmenu } = await import('../../00group-overlay/dees-contextmenu/dees-contextmenu.js');
|
|
127
207
|
const currentSessionId = event.detail.sessionId;
|
|
128
|
-
const eligibleSessionIds = [
|
|
208
|
+
const eligibleSessionIds = list.eligibleSessionIdsByResourceId[event.detail.resource.id] ?? [];
|
|
129
209
|
const menu = await DeesContextmenu.openContextMenuWithOptions(
|
|
130
210
|
new MouseEvent('contextmenu', {
|
|
131
211
|
clientX: event.detail.clientX,
|
|
@@ -136,7 +216,7 @@ const handleResourceContext = async (
|
|
|
136
216
|
.filter((sessionId) => sessionId !== currentSessionId)
|
|
137
217
|
.map((sessionId) => ({
|
|
138
218
|
name: `${currentSessionId ? 'Reassign' : 'Attach'} to ${
|
|
139
|
-
|
|
219
|
+
list.sessions.find((session) => session.id === sessionId)?.title
|
|
140
220
|
}`,
|
|
141
221
|
iconName: 'lucide:Link',
|
|
142
222
|
action: async () => {
|
|
@@ -159,73 +239,246 @@ const handleResourceContext = async (
|
|
|
159
239
|
if (!menu) {
|
|
160
240
|
event.detail.close();
|
|
161
241
|
} else {
|
|
162
|
-
|
|
163
|
-
if (menu.isConnected) return;
|
|
164
|
-
observer.disconnect();
|
|
165
|
-
event.detail.close();
|
|
166
|
-
});
|
|
167
|
-
observer.observe(document.body, { childList: true });
|
|
242
|
+
menu.registerGarbageFunction(() => event.detail.close());
|
|
168
243
|
}
|
|
169
244
|
list.selectedResourceId = event.detail.resource.id;
|
|
170
245
|
};
|
|
171
246
|
|
|
172
|
-
const openModal = async () => {
|
|
247
|
+
const openModal = async (sessions: IHarnessSessionMeta[], groups: IHarnessSessionGroup[]) => {
|
|
173
248
|
const { DeesModal } = await import('../../00group-overlay/dees-modal/dees-modal.js');
|
|
174
249
|
const content = document.createElement('dees-harness-session-list');
|
|
175
|
-
content.sessions =
|
|
250
|
+
content.sessions = sessions;
|
|
251
|
+
content.groups = groups;
|
|
252
|
+
content.ungroupedSessionIds = [];
|
|
253
|
+
content.enableGrouping = true;
|
|
176
254
|
content.style.height = '420px';
|
|
177
255
|
content.addEventListener('harness-session-select', handleSessionSelect as EventListener);
|
|
178
|
-
content.addEventListener('harness-session-open',
|
|
179
|
-
|
|
256
|
+
content.addEventListener('harness-session-open', handleSessionOpen as EventListener);
|
|
257
|
+
content.addEventListener('harness-session-context', (event) => {
|
|
258
|
+
void handleSessionContext(event as CustomEvent<IHarnessSessionContextDetail>);
|
|
180
259
|
});
|
|
260
|
+
content.addEventListener('harness-session-move', handleSessionMove as EventListener);
|
|
181
261
|
DeesModal.createAndShow({
|
|
182
262
|
heading: 'Conversations',
|
|
183
|
-
content: html
|
|
263
|
+
content: html`<style>${demoStyles}</style><div class="dees-demo-stack">${content}${demoOutput('Choose a conversation action.')}</div>`,
|
|
184
264
|
menuOptions: [],
|
|
185
265
|
});
|
|
186
266
|
};
|
|
187
267
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
268
|
+
@customElement('dees-demo-session-workspace')
|
|
269
|
+
class DeesDemoSessionWorkspace extends DeesElement {
|
|
270
|
+
@state() private accessor sessions = demoSessions();
|
|
271
|
+
@state() private accessor selectedSessionId = 'session-0';
|
|
272
|
+
@state() private accessor groups = demoGroups.map((group) => ({ ...group, sessionIds: [...group.sessionIds] }));
|
|
273
|
+
@state() private accessor ungroupedSessionIds: string[] = [];
|
|
274
|
+
@state() private accessor associations = [...demoResourceAssociations];
|
|
275
|
+
@state() private accessor notice = 'Interactive demo · Changes stay in this example.';
|
|
276
|
+
@state() private accessor showDetails = false;
|
|
277
|
+
|
|
278
|
+
public static styles = [themeDefaultStyles, demoStyles, harnessPillStyles, css`
|
|
279
|
+
:host { display: flex; flex-direction: column; height: 100%; min-height: 0; gap: 16px; container-type: inline-size; }
|
|
280
|
+
* { box-sizing: border-box; }
|
|
281
|
+
.topbar, .toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
282
|
+
.topbar { justify-content: space-between; }
|
|
283
|
+
h1 { margin: 0; font-size: 22px; font-weight: 620; letter-spacing: -.025em; }
|
|
284
|
+
.subtitle { margin: 4px 0 0; font-size: 12px; color: var(--dees-color-text-secondary); }
|
|
285
|
+
.workspace { display: grid; grid-template-columns: minmax(330px, 39%) minmax(0, 1fr); flex: 1; min-height: 0; border: 1px solid var(--dees-color-border-subtle); border-radius: var(--dees-radius-2xl); overflow: hidden; background: var(--dees-color-bg-primary); }
|
|
286
|
+
.sidebar { display: flex; flex-direction: column; min-height: 0; min-width: 0; padding-top: 8px; border-right: 1px solid var(--dees-color-border-subtle); background: var(--dees-color-bg-canvas); }
|
|
287
|
+
.sidebarSummary { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; font-size: 12px; font-weight: 600; }
|
|
288
|
+
.attentionSummary { display: inline-flex; align-items: center; gap: 5px; color: var(--dees-color-text-warning); font-size: 11px; }
|
|
289
|
+
dees-harness-session-list { flex: 1; min-height: 0; }
|
|
290
|
+
.sidebar output { margin: 0; padding: 10px 16px; min-height: 44px; font-size: 11px; line-height: 1.5; color: var(--dees-color-text-secondary); border-top: 1px solid var(--dees-color-border-subtle); }
|
|
291
|
+
.detail { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: auto; }
|
|
292
|
+
.detailHeader { padding: 24px 28px 20px; border-bottom: 1px solid var(--dees-color-border-subtle); }
|
|
293
|
+
.identity { font-size: 12px; color: var(--dees-color-text-secondary); margin-bottom: 12px; }
|
|
294
|
+
.detailHeader .harness-pill { text-transform: none; }
|
|
295
|
+
h2 { margin: 0 0 12px; font-size: 23px; line-height: 1.25; font-weight: 600; letter-spacing: -.025em; overflow-wrap: anywhere; }
|
|
296
|
+
h2:focus-visible { outline: 2px solid var(--dees-color-focus-ring); outline-offset: 4px; border-radius: 4px; }
|
|
297
|
+
.detailBody { display: grid; align-content: start; gap: 20px; padding: 24px 28px; flex: 1; }
|
|
298
|
+
.description { font-size: 13px; line-height: 1.65; color: var(--dees-color-text-secondary); margin: 0; }
|
|
299
|
+
.activity { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
|
|
300
|
+
.activity li { display: flex; align-items: center; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--dees-color-border-subtle); font-size: 12px; color: var(--dees-color-text-secondary); }
|
|
301
|
+
.activity dees-icon { color: var(--dees-color-text-success); }
|
|
302
|
+
.activity .pending dees-icon { color: var(--dees-color-text-muted); }
|
|
303
|
+
.result { border-left: 3px solid var(--dees-color-text-success); padding: 10px 14px; background: var(--dees-color-bg-secondary); font-size: 12px; line-height: 1.6; border-radius: 0 var(--dees-radius-md) var(--dees-radius-md) 0; }
|
|
304
|
+
.error { border-left-color: var(--dees-color-text-error); }
|
|
305
|
+
.scenarioControls { display: grid; gap: 10px; padding: 20px 28px; background: var(--dees-color-bg-canvas); border-top: 1px solid var(--dees-color-border-subtle); }
|
|
306
|
+
.scenarioLabel { font-size: 11px; color: var(--dees-color-text-muted); }
|
|
307
|
+
.back { display: none; }
|
|
308
|
+
@container (max-width: 700px) {
|
|
309
|
+
.workspace { grid-template-columns: minmax(0, 1fr); }
|
|
310
|
+
.detail { display: none; }
|
|
311
|
+
.workspace.showDetails .sidebar { display: none; }
|
|
312
|
+
.workspace.showDetails .detail { display: flex; }
|
|
313
|
+
.sidebar { border-right: 0; }
|
|
314
|
+
.back { display: inline-block; margin-bottom: 14px; }
|
|
315
|
+
.detailHeader, .detailBody, .scenarioControls { padding: 20px; }
|
|
316
|
+
.topbar { gap: 12px; }
|
|
317
|
+
h2 { font-size: 21px; }
|
|
201
318
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
319
|
+
`];
|
|
320
|
+
|
|
321
|
+
private get list(): DeesHarnessSessionList | null {
|
|
322
|
+
return this.shadowRoot?.querySelector('dees-harness-session-list') ?? null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private syncList(): void {
|
|
326
|
+
const list = this.list;
|
|
327
|
+
if (!list) return;
|
|
328
|
+
this.selectedSessionId = list.selectedSessionId;
|
|
329
|
+
this.groups = list.groups;
|
|
330
|
+
this.ungroupedSessionIds = list.ungroupedSessionIds;
|
|
331
|
+
this.associations = list.resourceAssociations;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private async select(event: CustomEvent<{ session: IHarnessSessionMeta }>): Promise<void> {
|
|
335
|
+
handleSessionSelect(event);
|
|
336
|
+
this.selectedSessionId = event.detail.session.id;
|
|
337
|
+
await this.revealSession();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async revealSession(): Promise<void> {
|
|
341
|
+
this.showDetails = true;
|
|
342
|
+
await this.updateComplete;
|
|
343
|
+
if (this.list && getComputedStyle(this.list.parentElement!).display === 'none') {
|
|
344
|
+
this.shadowRoot?.querySelector<HTMLElement>('h2')?.focus();
|
|
205
345
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
<
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private async advance(stage: TDemoStage, outcome?: string): Promise<void> {
|
|
349
|
+
const selected = this.sessions.find((session) => session.id === this.selectedSessionId)!;
|
|
350
|
+
this.sessions = this.sessions.map((session) => session.id !== selected.id ? session : {
|
|
351
|
+
...session, stage, state: stages[stage].state, statusLabel: stages[stage].label,
|
|
352
|
+
preview: stages[stage].description, updatedAt: Date.now(), outcome,
|
|
353
|
+
});
|
|
354
|
+
this.notice = `${selected.title} · ${stages[stage].label}${outcome ? ` · ${outcome}` : ''}`;
|
|
355
|
+
await this.updateComplete;
|
|
356
|
+
this.shadowRoot?.querySelector<HTMLElement>('h2')?.focus({ preventScroll: true });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private reset(): void {
|
|
360
|
+
this.sessions = demoSessions();
|
|
361
|
+
this.selectedSessionId = 'session-0';
|
|
362
|
+
this.groups = demoGroups.map((group) => ({ ...group, sessionIds: [...group.sessionIds] }));
|
|
363
|
+
this.ungroupedSessionIds = [];
|
|
364
|
+
this.associations = [...demoResourceAssociations];
|
|
365
|
+
this.notice = 'Demo reset. Choose a session or respond to the approval request.';
|
|
366
|
+
if (this.list) {
|
|
367
|
+
this.list.search = '';
|
|
368
|
+
this.list.selectedResourceId = '';
|
|
369
|
+
this.list.expandedSessionIds = new Set();
|
|
370
|
+
this.list.collapsedGroupIds = new Set();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public render() {
|
|
375
|
+
const selected = this.sessions.find((session) => session.id === this.selectedSessionId) ?? this.sessions[0];
|
|
376
|
+
const current = stages[selected.stage];
|
|
377
|
+
const attentionCount = this.sessions.filter((session) => session.state === 'attention' || session.state === 'error').length;
|
|
378
|
+
const tone = { normal: 'default', working: 'running', attention: 'warning', error: 'error', finished: 'success' }[current.state];
|
|
379
|
+
return html`
|
|
380
|
+
<header class="topbar">
|
|
381
|
+
<div><h1>Sessions</h1><p class="subtitle">Follow the work. Step in when you’re needed.</p></div>
|
|
382
|
+
<div class="toolbar">
|
|
383
|
+
<dees-button size="sm" @clicked=${() => openModal(this.sessions, this.groups)}>Open session browser</dees-button>
|
|
384
|
+
<dees-button size="sm" type="ghost" @clicked=${this.reset}>Reset demo</dees-button>
|
|
385
|
+
</div>
|
|
386
|
+
</header>
|
|
387
|
+
<div class="workspace ${this.showDetails ? 'showDetails' : ''}">
|
|
388
|
+
<aside class="sidebar" aria-label="Sessions">
|
|
389
|
+
<div class="sidebarSummary"><span>All sessions <span class="scenarioLabel">${this.sessions.length}</span></span>
|
|
390
|
+
<span class="attentionSummary"><dees-icon icon="lucide:CircleAlert" iconSize="12"></dees-icon>${attentionCount} need attention</span>
|
|
391
|
+
</div>
|
|
392
|
+
<dees-harness-session-list
|
|
393
|
+
.sessions=${this.sessions} .groups=${this.groups} .ungroupedSessionIds=${this.ungroupedSessionIds}
|
|
394
|
+
.resources=${demoResources} .resourceAssociations=${this.associations}
|
|
395
|
+
.eligibleSessionIdsByResourceId=${demoResourceEligibleSessionIds} .enableGrouping=${true}
|
|
396
|
+
.selectedSessionId=${this.selectedSessionId}
|
|
397
|
+
@harness-session-select=${this.select} @harness-session-open=${this.select}
|
|
398
|
+
@harness-session-context=${(event: CustomEvent<IHarnessSessionContextDetail>) => handleSessionContext(event, (opened) => {
|
|
399
|
+
this.syncList();
|
|
400
|
+
if (opened) void this.revealSession();
|
|
401
|
+
})}
|
|
402
|
+
@harness-session-move=${(event: CustomEvent<IHarnessSessionMoveDetail>) => { handleSessionMove(event); this.syncList(); }}
|
|
403
|
+
@harness-group-create-request=${() => {
|
|
404
|
+
this.groups = [...this.groups, { id: `group-${this.groups.length}`, name: `Group ${this.groups.length + 1}`, sessionIds: [] }];
|
|
405
|
+
if (this.list) this.list.search = '';
|
|
406
|
+
this.notice = 'Group created. Drag a session here or use its Actions menu.';
|
|
407
|
+
}}
|
|
408
|
+
@harness-resource-select=${(event: CustomEvent<IHarnessResourceEventDetail>) => {
|
|
409
|
+
(event.currentTarget as TDemoSessionList).selectedResourceId = event.detail.resource.id;
|
|
410
|
+
this.notice = `Selected ${event.detail.resource.title}. Use Actions to attach or move it.`;
|
|
411
|
+
}}
|
|
412
|
+
@harness-resource-context=${handleResourceContext}
|
|
413
|
+
@harness-resource-association-request=${(event: CustomEvent<THarnessResourceAssociationRequestDetail>) => { handleResourceAssociation(event); this.syncList(); }}
|
|
414
|
+
></dees-harness-session-list>
|
|
415
|
+
<output role="status">${this.notice}</output>
|
|
416
|
+
</aside>
|
|
417
|
+
<section class="detail" aria-label="Selected session">
|
|
418
|
+
<header class="detailHeader">
|
|
419
|
+
<dees-button class="back" size="sm" type="ghost" icon="lucide:ChevronLeft" @clicked=${async () => {
|
|
420
|
+
this.showDetails = false;
|
|
421
|
+
await this.updateComplete;
|
|
422
|
+
this.list?.shadowRoot?.querySelector<HTMLElement>(`[data-session-id="${this.selectedSessionId}"] .itemPrimary`)?.focus();
|
|
423
|
+
}}>All sessions</dees-button>
|
|
424
|
+
<div class="identity">${selected.harness?.name} <span aria-hidden="true">·</span> design.estate</div>
|
|
425
|
+
<h2 tabindex="-1">${selected.title}</h2>
|
|
426
|
+
<span class="harness-pill tone-${tone}">${current.label}</span>
|
|
427
|
+
</header>
|
|
428
|
+
<div class="detailBody">
|
|
429
|
+
<p class="description">${current.description}</p>
|
|
430
|
+
${directives.keyed(`${selected.id}:${selected.stage}:${selected.updatedAt}`, html`
|
|
431
|
+
${selected.stage === 'feedback' ? html`
|
|
432
|
+
<dees-harness-question-card .suppressEntry=${true} .request=${{
|
|
433
|
+
id: `${selected.id}:feedback`, sessionId: selected.id,
|
|
434
|
+
question: 'How should we handle the compact layout?',
|
|
435
|
+
options: [
|
|
436
|
+
{ label: 'Keep a compact sidebar', description: 'Preserve quick access to sessions on wider screens.' },
|
|
437
|
+
{ label: 'Use a focused single pane', description: 'Give the active task all available space.' },
|
|
438
|
+
],
|
|
439
|
+
}} @harness-question-response=${(event: CustomEvent<IHarnessQuestionResponseDetail>) => {
|
|
440
|
+
void this.advance('working', `Feedback received: ${event.detail.answers.join(', ')}`);
|
|
441
|
+
}}></dees-harness-question-card>
|
|
442
|
+
` : selected.stage === 'approval' ? html`
|
|
443
|
+
<dees-harness-permission-card .suppressEntry=${true} .request=${{
|
|
444
|
+
id: `${selected.id}:approval`, sessionId: selected.id, title: 'Run the test suite',
|
|
445
|
+
type: 'shell', metadata: { command: 'pnpm test', cwd: '/workspace/design.estate' },
|
|
446
|
+
}} @harness-permission-response=${(event: CustomEvent<IHarnessPermissionResponseDetail>) => {
|
|
447
|
+
void this.advance(event.detail.response === 'reject' ? 'paused' : 'working',
|
|
448
|
+
event.detail.response === 'reject' ? 'Permission declined.' : 'Permission granted. Continuing the checks.');
|
|
449
|
+
}}></dees-harness-permission-card>
|
|
450
|
+
` : ''}
|
|
451
|
+
`)}
|
|
452
|
+
${selected.outcome ? html`<div class="result">${selected.outcome}</div>` : ''}
|
|
453
|
+
${selected.stage === 'error' ? html`<div class="result error"><strong>Model request failed</strong><br />HTTP 429 · Request limit reached.<br />No changes were lost.</div>` : ''}
|
|
454
|
+
${['idle', 'starting', 'working', 'finished'].includes(selected.stage) ? html`
|
|
455
|
+
<ol class="activity" aria-label="Work progress">
|
|
456
|
+
${['Prepare workspace', 'Inspect and update the implementation', 'Run checks and summarize'].map((label, index) => {
|
|
457
|
+
const complete = selected.stage === 'finished' || (selected.stage === 'working' && index === 0);
|
|
458
|
+
return html`<li class=${complete ? '' : 'pending'}><dees-icon aria-hidden="true" icon=${complete ? 'lucide:CircleCheck' : 'lucide:CircleDashed'} iconSize="16"></dees-icon>${label}${complete ? html`<span class="scenarioLabel">Done</span>` : ''}</li>`;
|
|
459
|
+
})}
|
|
460
|
+
</ol>
|
|
461
|
+
` : ''}
|
|
462
|
+
</div>
|
|
463
|
+
<footer class="scenarioControls">
|
|
464
|
+
<div class="scenarioLabel">Demo controls · Advance this session</div>
|
|
465
|
+
<div class="toolbar">
|
|
466
|
+
${selected.stage === 'idle' || selected.stage === 'paused' || selected.stage === 'finished' || selected.stage === 'error' ? html`
|
|
467
|
+
<dees-button size="sm" type="accent" @clicked=${() => this.advance('starting')}>${selected.stage === 'error' ? 'Retry session' : selected.stage === 'finished' ? 'Start another run' : 'Start work'}</dees-button>
|
|
468
|
+
` : selected.stage === 'starting' ? html`
|
|
469
|
+
<dees-button size="sm" type="accent" @clicked=${() => this.advance('working')}>Begin task</dees-button>
|
|
470
|
+
` : selected.stage === 'working' ? html`
|
|
471
|
+
<dees-button size="sm" type="accent" @clicked=${() => this.advance('feedback')}>Ask for feedback</dees-button>
|
|
472
|
+
<dees-button size="sm" @clicked=${() => this.advance('approval')}>Request approval</dees-button>
|
|
473
|
+
<dees-button size="sm" @clicked=${() => this.advance('finished')}>Complete work</dees-button>
|
|
474
|
+
<dees-button size="sm" type="ghost" @clicked=${() => this.advance('error')}>Simulate failure</dees-button>
|
|
475
|
+
` : html`<span class="description">Respond above to continue the session.</span>`}
|
|
476
|
+
</div>
|
|
477
|
+
</footer>
|
|
478
|
+
</section>
|
|
479
|
+
</div>
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export const demoFunc = () => demoApp(html`<dees-demo-session-workspace></dees-demo-session-workspace>`);
|