@design.estate/dees-catalog 10.0.0 → 11.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.
Files changed (26) hide show
  1. package/dist_bundle/bundle.js +914 -780
  2. package/dist_bundle/bundle.js.map +1 -1
  3. package/dist_ts_web/00_commitinfo_data.js +1 -1
  4. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +24 -1
  5. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +13 -1
  6. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +62 -48
  7. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.js +97 -57
  8. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +541 -666
  9. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +19 -1
  10. package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.d.ts +45 -6
  11. package/dist_ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.js +184 -27
  12. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +59 -20
  13. package/dist_ts_web/services/DeesServiceLibLoader.d.ts +7 -0
  14. package/dist_ts_web/services/DeesServiceLibLoader.js +45 -22
  15. package/package.json +1 -2
  16. package/readme.md +37 -17
  17. package/ts_web/00_commitinfo_data.ts +1 -1
  18. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +23 -0
  19. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +12 -0
  20. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +107 -55
  21. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +622 -756
  22. package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +18 -0
  23. package/ts_web/elements/00group-harness/dees-terminal-view/dees-terminal-view.ts +196 -24
  24. package/ts_web/elements/00group-harness/interfaces.ts +63 -19
  25. package/ts_web/services/DeesServiceLibLoader.ts +150 -102
  26. package/.smartconfig.json +0 -86
@@ -7,14 +7,16 @@ import '../dees-harness-question-card/dees-harness-question-card.js';
7
7
  import '../dees-harness-permission-card/dees-harness-permission-card.js';
8
8
  import type { DeesHarnessSessionList } from './dees-harness-session-list.js';
9
9
  import type {
10
+ IHarnessItemMoveDetail,
10
11
  IHarnessResourceAssociation,
11
12
  IHarnessResourceContextDetail,
12
13
  IHarnessResourceEventDetail,
13
14
  IHarnessResourceMeta,
15
+ IHarnessSessionEventDetail,
14
16
  IHarnessSessionGroup,
15
17
  IHarnessSessionContextDetail,
18
+ IHarnessSessionListItemRef,
16
19
  IHarnessSessionMeta,
17
- IHarnessSessionMoveDetail,
18
20
  IHarnessQuestionResponseDetail,
19
21
  IHarnessPermissionResponseDetail,
20
22
  THarnessResourceAssociationRequestDetail,
@@ -38,13 +40,13 @@ const stages: Record<TDemoStage, { state: NonNullable<IHarnessSessionMeta['state
38
40
  };
39
41
 
40
42
  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 },
43
+ { title: 'Fix sitemap cache key', harness: { id: 'codex', name: 'Codex' }, projectLabel: 'storefront', stage: 'approval' as const },
44
+ { title: 'Investigate model rate limits', harness: { id: 'opencode', name: 'OpenCode' }, projectLabel: 'gateway', stage: 'working' as const },
45
+ { title: 'Refactor workbench layout', harness: { id: 'flexharness', name: 'FlexHarness' }, projectLabel: 'storefront', stage: 'feedback' as const },
46
+ { title: 'Add adaptive code diffs', harness: { id: 'codex', name: 'Codex' }, projectLabel: 'catalog', stage: 'finished' as const },
47
+ { title: 'Prepare the catalog release', harness: { id: 'opencode', name: 'OpenCode' }, projectLabel: 'catalog', stage: 'error' as const },
46
48
  { 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 },
49
+ { title: 'Draft the migration guide', harness: { id: 'codex', name: 'Codex' }, projectLabel: 'gateway', stage: 'starting' as const },
48
50
  ].map((session, index) => ({
49
51
  ...session,
50
52
  id: `session-${index}`,
@@ -55,11 +57,39 @@ const demoSessions = (): IDemoSession[] => [
55
57
  preview: stages[session.stage].description,
56
58
  }));
57
59
 
60
+ // Resources are peers of sessions: `terminal:build` sits in Workspace while it
61
+ // stays attached to a session in Recent.
58
62
  const demoGroups: IHarnessSessionGroup[] = [
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'] },
63
+ {
64
+ id: 'active',
65
+ name: 'Workspace',
66
+ itemIds: [
67
+ { kind: 'session', id: 'session-0' },
68
+ { kind: 'resource', id: 'terminal:build' },
69
+ { kind: 'session', id: 'session-1' },
70
+ { kind: 'session', id: 'session-2' },
71
+ { kind: 'session', id: 'session-6' },
72
+ ],
73
+ },
74
+ {
75
+ id: 'review',
76
+ name: 'Recent',
77
+ itemIds: [
78
+ { kind: 'session', id: 'session-3' },
79
+ { kind: 'session', id: 'session-5' },
80
+ { kind: 'session', id: 'session-4' },
81
+ ],
82
+ },
61
83
  ];
62
84
 
85
+ const cloneGroups = (groupsArg: IHarnessSessionGroup[]): IHarnessSessionGroup[] =>
86
+ groupsArg.map((group) => ({ ...group, itemIds: group.itemIds.map((ref) => ({ ...ref })) }));
87
+
88
+ const sameRef = (
89
+ leftArg: IHarnessSessionListItemRef,
90
+ rightArg: IHarnessSessionListItemRef,
91
+ ): boolean => leftArg.kind === rightArg.kind && leftArg.id === rightArg.id;
92
+
63
93
  const demoResources: IHarnessResourceMeta[] = [
64
94
  {
65
95
  id: 'terminal:build',
@@ -67,6 +97,7 @@ const demoResources: IHarnessResourceMeta[] = [
67
97
  title: 'Build shell',
68
98
  icon: 'lucide:Terminal',
69
99
  preview: 'pnpm run build',
100
+ projectLabel: 'storefront',
70
101
  updatedAt: Date.now() - 90_000,
71
102
  state: 'ready',
72
103
  },
@@ -76,6 +107,7 @@ const demoResources: IHarnessResourceMeta[] = [
76
107
  title: 'Release notes',
77
108
  icon: 'lucide:FileText',
78
109
  preview: 'Catalog migration notes',
110
+ projectLabel: 'catalog',
79
111
  updatedAt: Date.now() - 180_000,
80
112
  },
81
113
  {
@@ -88,45 +120,61 @@ const demoResources: IHarnessResourceMeta[] = [
88
120
  },
89
121
  ];
90
122
 
123
+ // An association targets any other item: a session, or a resource such as a terminal.
91
124
  const demoResourceAssociations: IHarnessResourceAssociation[] = [
92
- { resourceId: 'terminal:build', sessionId: 'session-4' },
93
- { resourceId: 'file:release-notes', sessionId: 'session-4' },
125
+ { resourceId: 'terminal:build', target: { kind: 'session', id: 'session-4' } },
126
+ { resourceId: 'file:release-notes', target: { kind: 'resource', id: 'terminal:build' } },
127
+ ];
128
+
129
+ const demoEligibleSessionTargets: IHarnessSessionListItemRef[] = [
130
+ { kind: 'session', id: 'session-0' },
131
+ { kind: 'session', id: 'session-1' },
132
+ { kind: 'session', id: 'session-2' },
133
+ { kind: 'session', id: 'session-4' },
94
134
  ];
95
135
 
96
- const demoResourceEligibleSessionIds = Object.fromEntries(
97
- demoResources.map((resource) => [resource.id, ['session-0', 'session-1', 'session-2', 'session-4']]),
136
+ const demoResourceEligibleTargets: Record<string, IHarnessSessionListItemRef[]> = Object.fromEntries(
137
+ demoResources.map((resource) => [
138
+ resource.id,
139
+ resource.kind === 'terminal'
140
+ ? demoEligibleSessionTargets
141
+ : [...demoEligibleSessionTargets, { kind: 'resource' as const, id: 'terminal:build' }],
142
+ ]),
98
143
  );
99
144
 
100
145
  type TDemoSessionList = DeesHarnessSessionList;
101
146
 
102
- const handleSessionSelect = (event: CustomEvent<{ session: IHarnessSessionMeta }>): void => {
147
+ const handleSessionSelect = (event: CustomEvent<IHarnessSessionEventDetail>): void => {
103
148
  const list = event.currentTarget as TDemoSessionList;
104
149
  list.selectedSessionId = event.detail.session.id;
105
150
  };
106
151
 
107
- const handleSessionMove = (event: CustomEvent<IHarnessSessionMoveDetail>): void => {
152
+ /** The host owns order: one reducer moves sessions and resources alike. */
153
+ type TDemoItemMove = Pick<IHarnessItemMoveDetail, 'item' | 'toGroupId' | 'beforeItem'>;
154
+
155
+ const handleItemMove = (event: CustomEvent<IHarnessItemMoveDetail>): void => {
108
156
  const list = event.currentTarget as TDemoSessionList;
109
- applySessionMove(list, event.detail);
157
+ applyItemMove(list, event.detail);
110
158
  };
111
159
 
112
- const applySessionMove = (list: TDemoSessionList, detail: IHarnessSessionMoveDetail): void => {
113
- const { sessionId, groupId, beforeSessionId } = detail;
160
+ const applyItemMove = (list: TDemoSessionList, moveArg: TDemoItemMove): void => {
161
+ const { item, toGroupId, beforeItem } = moveArg;
114
162
  const groups = list.groups.map((group) => ({
115
163
  ...group,
116
- sessionIds: group.sessionIds.filter((id) => id !== sessionId),
164
+ itemIds: group.itemIds.filter((ref) => !sameRef(ref, item)),
117
165
  }));
118
- const ungroupedSessionIds = list.ungroupedSessionIds.filter((id) => id !== sessionId);
119
- const targetIds = groupId === null
120
- ? ungroupedSessionIds
121
- : groups.find((group) => group.id === groupId)?.sessionIds;
166
+ const ungroupedItemIds = list.ungroupedItemIds.filter((ref) => !sameRef(ref, item));
167
+ const targetIds = toGroupId === null
168
+ ? ungroupedItemIds
169
+ : groups.find((group) => group.id === toGroupId)?.itemIds;
122
170
  if (!targetIds) return;
123
- const anchorIndex = beforeSessionId ? targetIds.indexOf(beforeSessionId) : -1;
124
- targetIds.splice(anchorIndex >= 0 ? anchorIndex : targetIds.length, 0, sessionId);
171
+ const anchorIndex = beforeItem ? targetIds.findIndex((ref) => sameRef(ref, beforeItem)) : -1;
172
+ targetIds.splice(anchorIndex >= 0 ? anchorIndex : targetIds.length, 0, { ...item });
125
173
  list.groups = groups;
126
- list.ungroupedSessionIds = ungroupedSessionIds;
174
+ list.ungroupedItemIds = ungroupedItemIds;
127
175
  };
128
176
 
129
- const handleSessionOpen = (event: CustomEvent<{ session: IHarnessSessionMeta }>): void => {
177
+ const handleSessionOpen = (event: CustomEvent<IHarnessSessionEventDetail>): void => {
130
178
  const list = event.currentTarget as TDemoSessionList;
131
179
  list.selectedSessionId = event.detail.session.id;
132
180
  showDemoResult(list, `Opened “${event.detail.session.title}”.`);
@@ -141,7 +189,10 @@ const handleSessionContext = async (
141
189
  const invokingControl = originalEvent.composedPath().find(
142
190
  (target): target is HTMLButtonElement => target instanceof HTMLButtonElement,
143
191
  );
144
- const currentGroup = list.groups.find((group) => group.sessionIds.includes(session.id));
192
+ const sessionRef: IHarnessSessionListItemRef = { kind: 'session', id: session.id };
193
+ const currentGroup = list.groups.find(
194
+ (group) => group.itemIds.some((ref) => sameRef(ref, sessionRef)),
195
+ );
145
196
  // Capture dialog ownership before event dispatch ends: no await before opening.
146
197
  const menu = await DeesContextmenu.openContextMenuWithOptions(originalEvent, [
147
198
  {
@@ -159,7 +210,7 @@ const handleSessionContext = async (
159
210
  ...list.groups.filter((group) => group.id !== currentGroup?.id).map((group) => ({
160
211
  name: `Move to ${group.name}`, iconName: 'lucide:FolderInput',
161
212
  action: async () => {
162
- applySessionMove(list, { sessionId: session.id, groupId: group.id, index: group.sessionIds.length });
213
+ applyItemMove(list, { item: sessionRef, toGroupId: group.id, beforeItem: null });
163
214
  onChange();
164
215
  showDemoResult(list, `Moved “${session.title}” to ${group.name}.`);
165
216
  },
@@ -167,7 +218,7 @@ const handleSessionContext = async (
167
218
  ...(currentGroup ? [{
168
219
  name: 'Move to Ungrouped', iconName: 'lucide:FolderInput',
169
220
  action: async () => {
170
- applySessionMove(list, { sessionId: session.id, groupId: null, index: list.ungroupedSessionIds.length });
221
+ applyItemMove(list, { item: sessionRef, toGroupId: null, beforeItem: null });
171
222
  onChange();
172
223
  showDemoResult(list, `Moved “${session.title}” to Ungrouped.`);
173
224
  },
@@ -191,10 +242,10 @@ const handleResourceAssociation = (
191
242
  const next = list.resourceAssociations.filter(
192
243
  (association) => association.resourceId !== event.detail.resource.id,
193
244
  );
194
- if (event.detail.toSessionId) {
245
+ if (event.detail.toTarget) {
195
246
  next.push({
196
247
  resourceId: event.detail.resource.id,
197
- sessionId: event.detail.toSessionId,
248
+ target: { ...event.detail.toTarget },
198
249
  });
199
250
  }
200
251
  list.resourceAssociations = next;
@@ -204,27 +255,28 @@ const handleResourceContext = async (
204
255
  event: CustomEvent<IHarnessResourceContextDetail>,
205
256
  ): Promise<void> => {
206
257
  const list = event.currentTarget as TDemoSessionList;
207
- const currentSessionId = event.detail.sessionId;
208
- const eligibleSessionIds = list.eligibleSessionIdsByResourceId[event.detail.resource.id] ?? [];
258
+ const currentTarget = event.detail.target;
259
+ const eligibleTargets = list.eligibleTargetsByResourceId[event.detail.resource.id] ?? [];
260
+ const titleFor = (refArg: IHarnessSessionListItemRef): string => (refArg.kind === 'session'
261
+ ? list.sessions.find((session) => session.id === refArg.id)?.title
262
+ : list.resources.find((resource) => resource.id === refArg.id)?.title) ?? refArg.id;
209
263
  const menu = await DeesContextmenu.openContextMenuWithOptions(
210
264
  new MouseEvent('contextmenu', {
211
265
  clientX: event.detail.clientX,
212
266
  clientY: event.detail.clientY,
213
267
  }),
214
268
  [
215
- ...eligibleSessionIds
216
- .filter((sessionId) => sessionId !== currentSessionId)
217
- .map((sessionId) => ({
218
- name: `${currentSessionId ? 'Reassign' : 'Attach'} to ${
219
- list.sessions.find((session) => session.id === sessionId)?.title
220
- }`,
269
+ ...eligibleTargets
270
+ .filter((target) => !currentTarget || !sameRef(target, currentTarget))
271
+ .map((target) => ({
272
+ name: `${currentTarget ? 'Reassign' : 'Attach'} to ${titleFor(target)}`,
221
273
  iconName: 'lucide:Link',
222
274
  action: async () => {
223
- event.detail.requestAssociation(sessionId);
275
+ event.detail.requestAssociation(target);
224
276
  event.detail.close();
225
277
  },
226
278
  })),
227
- ...(currentSessionId
279
+ ...(currentTarget
228
280
  ? [{
229
281
  name: 'Detach',
230
282
  iconName: 'lucide:Unlink',
@@ -249,7 +301,7 @@ const openModal = async (sessions: IHarnessSessionMeta[], groups: IHarnessSessio
249
301
  const content = document.createElement('dees-harness-session-list');
250
302
  content.sessions = sessions;
251
303
  content.groups = groups;
252
- content.ungroupedSessionIds = [];
304
+ content.ungroupedItemIds = [];
253
305
  content.enableGrouping = true;
254
306
  content.style.height = '420px';
255
307
  content.addEventListener('harness-session-select', handleSessionSelect as EventListener);
@@ -257,7 +309,7 @@ const openModal = async (sessions: IHarnessSessionMeta[], groups: IHarnessSessio
257
309
  content.addEventListener('harness-session-context', (event) => {
258
310
  void handleSessionContext(event as CustomEvent<IHarnessSessionContextDetail>);
259
311
  });
260
- content.addEventListener('harness-session-move', handleSessionMove as EventListener);
312
+ content.addEventListener('harness-item-move', handleItemMove as EventListener);
261
313
  DeesModal.createAndShow({
262
314
  heading: 'Conversations',
263
315
  content: html`<style>${demoStyles}</style><div class="dees-demo-stack">${content}${demoOutput('Choose a conversation action.')}</div>`,
@@ -269,8 +321,8 @@ const openModal = async (sessions: IHarnessSessionMeta[], groups: IHarnessSessio
269
321
  class DeesDemoSessionWorkspace extends DeesElement {
270
322
  @state() private accessor sessions = demoSessions();
271
323
  @state() private accessor selectedSessionId = 'session-0';
272
- @state() private accessor groups = demoGroups.map((group) => ({ ...group, sessionIds: [...group.sessionIds] }));
273
- @state() private accessor ungroupedSessionIds: string[] = [];
324
+ @state() private accessor groups: IHarnessSessionGroup[] = cloneGroups(demoGroups);
325
+ @state() private accessor ungroupedItemIds: IHarnessSessionListItemRef[] = [];
274
326
  @state() private accessor associations = [...demoResourceAssociations];
275
327
  @state() private accessor notice = 'Interactive demo · Changes stay in this example.';
276
328
  @state() private accessor showDetails = false;
@@ -327,11 +379,11 @@ class DeesDemoSessionWorkspace extends DeesElement {
327
379
  if (!list) return;
328
380
  this.selectedSessionId = list.selectedSessionId;
329
381
  this.groups = list.groups;
330
- this.ungroupedSessionIds = list.ungroupedSessionIds;
382
+ this.ungroupedItemIds = list.ungroupedItemIds;
331
383
  this.associations = list.resourceAssociations;
332
384
  }
333
385
 
334
- private async select(event: CustomEvent<{ session: IHarnessSessionMeta }>): Promise<void> {
386
+ private async select(event: CustomEvent<IHarnessSessionEventDetail>): Promise<void> {
335
387
  handleSessionSelect(event);
336
388
  this.selectedSessionId = event.detail.session.id;
337
389
  await this.revealSession();
@@ -359,8 +411,8 @@ class DeesDemoSessionWorkspace extends DeesElement {
359
411
  private reset(): void {
360
412
  this.sessions = demoSessions();
361
413
  this.selectedSessionId = 'session-0';
362
- this.groups = demoGroups.map((group) => ({ ...group, sessionIds: [...group.sessionIds] }));
363
- this.ungroupedSessionIds = [];
414
+ this.groups = cloneGroups(demoGroups);
415
+ this.ungroupedItemIds = [];
364
416
  this.associations = [...demoResourceAssociations];
365
417
  this.notice = 'Demo reset. Choose a session or respond to the approval request.';
366
418
  if (this.list) {
@@ -390,20 +442,20 @@ class DeesDemoSessionWorkspace extends DeesElement {
390
442
  <span class="attentionSummary"><dees-icon icon="lucide:CircleAlert" iconSize="12"></dees-icon>${attentionCount} need attention</span>
391
443
  </div>
392
444
  <dees-harness-session-list
393
- .sessions=${this.sessions} .groups=${this.groups} .ungroupedSessionIds=${this.ungroupedSessionIds}
445
+ .sessions=${this.sessions} .groups=${this.groups} .ungroupedItemIds=${this.ungroupedItemIds}
394
446
  .resources=${demoResources} .resourceAssociations=${this.associations}
395
- .eligibleSessionIdsByResourceId=${demoResourceEligibleSessionIds} .enableGrouping=${true}
447
+ .eligibleTargetsByResourceId=${demoResourceEligibleTargets} .enableGrouping=${true}
396
448
  .selectedSessionId=${this.selectedSessionId}
397
449
  @harness-session-select=${this.select} @harness-session-open=${this.select}
398
450
  @harness-session-context=${(event: CustomEvent<IHarnessSessionContextDetail>) => handleSessionContext(event, (opened) => {
399
451
  this.syncList();
400
452
  if (opened) void this.revealSession();
401
453
  })}
402
- @harness-session-move=${(event: CustomEvent<IHarnessSessionMoveDetail>) => { handleSessionMove(event); this.syncList(); }}
454
+ @harness-item-move=${(event: CustomEvent<IHarnessItemMoveDetail>) => { handleItemMove(event); this.syncList(); }}
403
455
  @harness-group-create-request=${() => {
404
- this.groups = [...this.groups, { id: `group-${this.groups.length}`, name: `Group ${this.groups.length + 1}`, sessionIds: [] }];
456
+ this.groups = [...this.groups, { id: `group-${this.groups.length}`, name: `Group ${this.groups.length + 1}`, itemIds: [] }];
405
457
  if (this.list) this.list.search = '';
406
- this.notice = 'Group created. Drag a session here or use its Actions menu.';
458
+ this.notice = 'Group created. Drag a conversation or resource here, or use its Actions menu.';
407
459
  }}
408
460
  @harness-resource-select=${(event: CustomEvent<IHarnessResourceEventDetail>) => {
409
461
  (event.currentTarget as TDemoSessionList).selectedResourceId = event.detail.resource.id;