@design.estate/dees-catalog 3.92.0 → 3.93.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-catalog",
3
- "version": "3.92.0",
3
+ "version": "3.93.0",
4
4
  "private": false,
5
5
  "description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
6
6
  "main": "dist_ts_web/index.js",
package/readme.md CHANGED
@@ -1721,7 +1721,9 @@ chat.applyDelta({ type: 'text', messageId: 'm1', delta: 'chunk' });
1721
1721
  chat.applyDelta({ type: 'message-end', messageId: 'm1', usage: { totalTokens: 1200 } });
1722
1722
  ```
1723
1723
 
1724
- Key props: `messages`, `permissions`, `status`, `usage`, `heading`, `subheading`, `showToolbar`, `busy`, `queuedCount`, `disabled`, `model`/`modelOptions`, `reasoningEffort`/`effortOptions`, `toolRegistry`. Methods: `applyDelta()`, `focusComposer()`, `clearComposer()`, `scrollToBottom()`. Events (also from children, all composed): `harness-send`, `harness-steer`, `harness-abort`, `harness-input`, `harness-attachments-change`, `harness-attachment-error`, `harness-model-change`, `harness-permission-response`, `harness-new-session`, `harness-open-sessions`.
1724
+ Key props: `messages`, `permissions`, `status`, `usage`, `todos`/`showTodosPanel`, `heading`, `subheading`, `showToolbar`, `busy`, `queuedCount`, `disabled`, `model`/`modelOptions`, `reasoningEffort`/`effortOptions`, `toolRegistry`.
1725
+
1726
+ A **Tasks side panel** (OpenCode-style) shows the current todo list next to the transcript: set `.todos` explicitly, or leave it empty and the chat derives it from the newest todo tool call in `messages`. The panel hides itself in narrow containers (< 520px) where the transcript's todo cards carry the same information; `showTodosPanel={false}` disables it entirely. Methods: `applyDelta()`, `focusComposer()`, `clearComposer()`, `scrollToBottom()`. Events (also from children, all composed): `harness-send`, `harness-steer`, `harness-abort`, `harness-input`, `harness-attachments-change`, `harness-attachment-error`, `harness-model-change`, `harness-permission-response`, `harness-new-session`, `harness-open-sessions`.
1725
1727
 
1726
1728
  #### `DeesHarnessMessageList`
1727
1729
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '3.92.0',
6
+ version: '3.93.0',
7
7
  description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
8
8
  }
@@ -1,7 +1,7 @@
1
1
  import { html } from '@design.estate/dees-element';
2
2
  import type { DeesHarnessChat } from './dees-harness-chat.js';
3
3
  import type { IHarnessMessage, IHarnessPermissionRequest } from '../interfaces.js';
4
- import { harnessDemoConversation, makeStreamingSimulator } from '../harness.demodata.js';
4
+ import { harnessDemoConversation } from '../harness.demodata.js';
5
5
 
6
6
  const toolShowcase = (): IHarnessMessage[] => {
7
7
  const now = Date.now();
@@ -84,30 +84,143 @@ let stopStream: (() => void) | undefined;
84
84
  const chatOf = (event: MouseEvent): DeesHarnessChat | null =>
85
85
  (event.target as HTMLElement).closest('.chatDemo')?.querySelector('dees-harness-chat') ?? null;
86
86
 
87
+ /**
88
+ * Streams a realistic turn: reasoning, a todo write, a test run, a file
89
+ * edit rendered as a diff, then the streamed summary text.
90
+ */
87
91
  const simulate = (event: MouseEvent) => {
88
92
  const chat = chatOf(event);
89
93
  if (!chat) return;
90
94
  stopStream?.();
91
- const message: IHarnessMessage = {
92
- id: `stream-${Date.now()}`,
93
- role: 'assistant',
94
- text: '',
95
- createdAt: Date.now(),
96
- streaming: true,
95
+ const runId = Date.now();
96
+ const cancels: Array<() => void> = [];
97
+ const at = (ms: number, action: () => void) => {
98
+ const timeout = setTimeout(action, ms);
99
+ cancels.push(() => clearTimeout(timeout));
97
100
  };
98
- chat.messages = [...chat.messages, message];
99
- chat.status = { type: 'busy', message: 'Responding…' };
100
- stopStream = makeStreamingSimulator(
101
- {
102
- applyDelta: (delta) => {
103
- chat.applyDelta(delta);
104
- if (delta.type === 'message-end') {
105
- chat.status = { type: 'idle' };
106
- }
101
+ stopStream = () => {
102
+ for (const cancel of cancels) cancel();
103
+ chat.status = { type: 'idle' };
104
+ };
105
+ const pushMessage = (message: IHarnessMessage) => {
106
+ chat.messages = [...chat.messages, message];
107
+ };
108
+ const updateTool = (messageId: string, tool: Partial<IHarnessMessage['toolCall']> & { id: string }) => {
109
+ chat.applyDelta({ type: 'tool-update', messageId, tool: tool as never });
110
+ };
111
+
112
+ chat.status = { type: 'busy', message: 'Planning…' };
113
+
114
+ // 1) reasoning streams first
115
+ const reasoningMessage: IHarnessMessage = {
116
+ id: `sim-${runId}-reasoning`, role: 'assistant', text: '', createdAt: Date.now(), streaming: true,
117
+ };
118
+ pushMessage(reasoningMessage);
119
+ chat.applyDelta({ type: 'reasoning-start', messageId: reasoningMessage.id, partId: 'r1', startedAt: Date.now() });
120
+ const reasoningText = 'The cache key includes the locale list, so every rollout re-crawls. Plan: track the fix as todos, run the tests, then pin the key to the route manifest.';
121
+ reasoningText.split(' ').forEach((word, index) => {
122
+ at(80 + index * 45, () => chat.applyDelta({ type: 'reasoning', messageId: reasoningMessage.id, partId: 'r1', delta: word + ' ' }));
123
+ });
124
+ const reasoningDoneAt = 80 + reasoningText.split(' ').length * 45 + 150;
125
+ at(reasoningDoneAt, () => {
126
+ chat.applyDelta({ type: 'reasoning-end', messageId: reasoningMessage.id, partId: 'r1', endedAt: Date.now() });
127
+ chat.applyDelta({ type: 'message-end', messageId: reasoningMessage.id });
128
+ });
129
+
130
+ // 2) todo write
131
+ const todoId = `sim-${runId}-todos`;
132
+ at(reasoningDoneAt + 300, () => {
133
+ chat.status = { type: 'busy', message: 'Updating task list…' };
134
+ pushMessage({
135
+ id: todoId, role: 'tool', text: '', createdAt: Date.now(),
136
+ toolCall: {
137
+ id: `${todoId}-call`, name: 'todowrite', status: 'running',
138
+ input: { todos: [
139
+ { content: 'Pin sitemap cache key to route manifest hash', status: 'in_progress' },
140
+ { content: 'Run the sitemap test suite', status: 'pending' },
141
+ { content: 'Apply the cache-key patch', status: 'pending' },
142
+ { content: 'Verify staging timings', status: 'pending' },
143
+ ] },
144
+ startedAt: Date.now(),
107
145
  },
108
- },
109
- message.id,
110
- );
146
+ });
147
+ });
148
+ at(reasoningDoneAt + 1000, () => updateTool(todoId, { id: `${todoId}-call`, status: 'completed', finishedAt: Date.now() }));
149
+
150
+ // 3) test run
151
+ const testId = `sim-${runId}-test`;
152
+ at(reasoningDoneAt + 1400, () => {
153
+ chat.status = { type: 'busy', message: 'Running tests…' };
154
+ pushMessage({
155
+ id: testId, role: 'tool', text: '', createdAt: Date.now(),
156
+ toolCall: {
157
+ id: `${testId}-call`, name: 'run_command', status: 'running',
158
+ input: { command: 'pnpm test --filter sitemap', cwd: '/srv/app' },
159
+ startedAt: Date.now(),
160
+ },
161
+ });
162
+ });
163
+ at(reasoningDoneAt + 3200, () => updateTool(testId, {
164
+ id: `${testId}-call`, status: 'completed',
165
+ output: { exitCode: 0, stdout: '14 tests passed\n0 failed' },
166
+ finishedAt: Date.now(),
167
+ }));
168
+
169
+ // 4) file edit rendered as a diff
170
+ const editId = `sim-${runId}-edit`;
171
+ at(reasoningDoneAt + 3600, () => {
172
+ chat.status = { type: 'busy', message: 'Applying the patch…' };
173
+ pushMessage({
174
+ id: editId, role: 'tool', text: '', createdAt: Date.now(),
175
+ toolCall: {
176
+ id: `${editId}-call`, name: 'write_file', status: 'running',
177
+ input: {
178
+ path: '/srv/app/src/jobs/sitemap.job.ts',
179
+ oldContent: 'const cacheKey = hash(locales);\nconst ttl = 3600;',
180
+ content: 'const cacheKey = hash(routeManifest);\nconst ttl = 3600;',
181
+ },
182
+ startedAt: Date.now(),
183
+ },
184
+ });
185
+ });
186
+ at(reasoningDoneAt + 4600, () => updateTool(editId, { id: `${editId}-call`, status: 'completed', finishedAt: Date.now() }));
187
+
188
+ // 5) updated todos, then the streamed summary
189
+ const todoDoneId = `sim-${runId}-todos-2`;
190
+ at(reasoningDoneAt + 5000, () => {
191
+ pushMessage({
192
+ id: todoDoneId, role: 'tool', text: '', createdAt: Date.now(),
193
+ toolCall: {
194
+ id: `${todoDoneId}-call`, name: 'todowrite', status: 'completed',
195
+ input: { todos: [
196
+ { content: 'Pin sitemap cache key to route manifest hash', status: 'completed' },
197
+ { content: 'Run the sitemap test suite', status: 'completed' },
198
+ { content: 'Apply the cache-key patch', status: 'completed' },
199
+ { content: 'Verify staging timings', status: 'in_progress' },
200
+ ] },
201
+ startedAt: Date.now(), finishedAt: Date.now(),
202
+ },
203
+ });
204
+ });
205
+ const summaryId = `sim-${runId}-summary`;
206
+ at(reasoningDoneAt + 5400, () => {
207
+ chat.status = { type: 'busy', message: 'Responding…' };
208
+ pushMessage({ id: summaryId, role: 'assistant', text: '', createdAt: Date.now(), streaming: true });
209
+ const summary = 'Patched `sitemap.job.ts` to key the cache on the **route manifest hash** — tests pass (14/14). Staging verification is the last open task.';
210
+ let offset = 0;
211
+ const interval = setInterval(() => {
212
+ const chunk = summary.slice(offset, offset + 6);
213
+ offset += 6;
214
+ if (!chunk) {
215
+ clearInterval(interval);
216
+ chat.applyDelta({ type: 'message-end', messageId: summaryId, usage: { inputTokens: 2100, outputTokens: 260, totalTokens: 2360 } });
217
+ chat.status = { type: 'idle' };
218
+ return;
219
+ }
220
+ chat.applyDelta({ type: 'text', messageId: summaryId, delta: chunk });
221
+ }, 40);
222
+ cancels.push(() => clearInterval(interval));
223
+ });
111
224
  };
112
225
 
113
226
  const askPermission = (event: MouseEvent) => {
@@ -14,14 +14,17 @@ import type {
14
14
  IHarnessMessage,
15
15
  IHarnessPermissionRequest,
16
16
  IHarnessStatus,
17
+ IHarnessTodoItem,
17
18
  IHarnessUsage,
18
19
  THarnessStreamDelta,
19
20
  } from '../interfaces.js';
21
+ import { harnessParseTodos } from '../harness.helpers.js';
20
22
  import { DeesHarnessToolRegistry } from '../toolregistry.js';
21
23
  import { DeesHarnessMessageList } from '../dees-harness-message-list/dees-harness-message-list.js';
22
24
  import { DeesHarnessComposer } from '../dees-harness-composer/dees-harness-composer.js';
23
25
  import '../dees-harness-permission-card/dees-harness-permission-card.js';
24
26
  import '../dees-harness-status/dees-harness-status.js';
27
+ import '../dees-harness-todos/dees-harness-todos.js';
25
28
  import '../dees-harness-usage/dees-harness-usage.js';
26
29
  import '../../00group-button/dees-button/dees-button.js';
27
30
  import '../../00group-utility/dees-icon/dees-icon.js';
@@ -61,6 +64,17 @@ export class DeesHarnessChat extends DeesElement {
61
64
  @property({ attribute: false })
62
65
  accessor usage: IHarnessUsage = {};
63
66
 
67
+ /**
68
+ * Current task list for the side panel. When empty, the panel derives it
69
+ * from the newest todo tool call in `messages` (registry kind 'todo'), so
70
+ * hosts that stream todowrite calls get the sidebar for free.
71
+ */
72
+ @property({ attribute: false })
73
+ accessor todos: IHarnessTodoItem[] = [];
74
+
75
+ @property({ type: Boolean })
76
+ accessor showTodosPanel: boolean = true;
77
+
64
78
  @property()
65
79
  accessor heading: string = '';
66
80
 
@@ -116,6 +130,32 @@ export class DeesHarnessChat extends DeesElement {
116
130
  font-family: var(--dees-font-family);
117
131
  color: var(--dees-color-text-primary);
118
132
  background: var(--dees-color-bg-primary);
133
+ container-type: inline-size;
134
+ }
135
+
136
+ .chatBody {
137
+ flex: 1;
138
+ min-height: 0;
139
+ display: flex;
140
+ }
141
+
142
+ .todosPanel {
143
+ flex: 0 0 232px;
144
+ min-width: 0;
145
+ overflow-y: auto;
146
+ scrollbar-width: thin;
147
+ scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
148
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
149
+ border-left: 1px solid var(--dees-color-border-subtle);
150
+ background: var(--dees-color-bg-secondary);
151
+ }
152
+
153
+ /* narrow hosts (e.g. a sidebar chat) drop the panel; todo tool cards
154
+ in the transcript still carry the same information */
155
+ @container (max-width: 520px) {
156
+ .todosPanel {
157
+ display: none;
158
+ }
119
159
  }
120
160
 
121
161
  .toolbar {
@@ -158,7 +198,7 @@ export class DeesHarnessChat extends DeesElement {
158
198
  flex: 0 0 auto;
159
199
  }
160
200
 
161
- dees-harness-message-list {
201
+ .chatBody dees-harness-message-list {
162
202
  flex: 1;
163
203
  min-height: 0;
164
204
  }
@@ -214,6 +254,19 @@ export class DeesHarnessChat extends DeesElement {
214
254
  this.listElement?.scrollToBottom(force);
215
255
  }
216
256
 
257
+ public get effectiveTodos(): IHarnessTodoItem[] {
258
+ if (this.todos.length) return this.todos;
259
+ // the newest todo call is the current state — an empty write clears
260
+ // the panel instead of falling back to an older list
261
+ for (let index = this.messages.length - 1; index >= 0; index--) {
262
+ const call = this.messages[index]?.toolCall;
263
+ if (!call) continue;
264
+ if (this.toolRegistry.resolve(call).kind !== 'todo') continue;
265
+ return harnessParseTodos(call.input);
266
+ }
267
+ return [];
268
+ }
269
+
217
270
  private get listElement(): DeesHarnessMessageList | undefined {
218
271
  const element = this.shadowRoot?.querySelector('dees-harness-message-list');
219
272
  return element instanceof DeesHarnessMessageList ? element : undefined;
@@ -229,12 +282,21 @@ export class DeesHarnessChat extends DeesElement {
229
282
  public render(): TemplateResult {
230
283
  return html`
231
284
  ${this.showToolbar ? this.renderToolbar() : ''}
232
- <dees-harness-message-list
233
- .messages=${this.messages}
234
- .status=${idleStatus}
235
- .showRoles=${this.showRoles}
236
- .toolRegistry=${this.toolRegistry}
237
- ></dees-harness-message-list>
285
+ <div class="chatBody">
286
+ <dees-harness-message-list
287
+ .messages=${this.messages}
288
+ .status=${idleStatus}
289
+ .showRoles=${this.showRoles}
290
+ .toolRegistry=${this.toolRegistry}
291
+ ></dees-harness-message-list>
292
+ ${this.showTodosPanel && this.effectiveTodos.length
293
+ ? html`
294
+ <aside class="todosPanel">
295
+ <dees-harness-todos heading="Tasks" .todos=${this.effectiveTodos}></dees-harness-todos>
296
+ </aside>
297
+ `
298
+ : ''}
299
+ </div>
238
300
  ${this.permissions.length
239
301
  ? html`
240
302
  <div class="permissionQueue">