@design.estate/dees-catalog 3.92.0 → 3.94.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 (42) hide show
  1. package/dist_bundle/bundle.js +2125 -1392
  2. package/dist_ts_web/00_commitinfo_data.js +1 -1
  3. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +15 -3
  4. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +159 -18
  5. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +126 -49
  6. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +13 -0
  7. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +5 -2
  8. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +118 -3
  9. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +25 -3
  10. package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +156 -48
  11. package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.d.ts +2 -0
  12. package/dist_ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.js +9 -5
  13. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.d.ts +31 -0
  14. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.d.ts +1 -0
  15. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.js +69 -0
  16. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.js +382 -0
  17. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/index.d.ts +1 -0
  18. package/dist_ts_web/elements/00group-harness/dees-harness-question-card/index.js +2 -0
  19. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  20. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  21. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +33 -0
  22. package/dist_ts_web/elements/00group-harness/toolregistry.js +9 -1
  23. package/dist_ts_web/elements/00theme.js +6 -1
  24. package/dist_watch/bundle.js +2126 -1393
  25. package/dist_watch/bundle.js.map +4 -4
  26. package/package.json +1 -1
  27. package/readme.md +14 -4
  28. package/readme.playbook.md +1 -1
  29. package/ts_web/00_commitinfo_data.ts +1 -1
  30. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +168 -21
  31. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +98 -49
  32. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +4 -1
  33. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +99 -0
  34. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +148 -46
  35. package/ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.ts +9 -4
  36. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.ts +70 -0
  37. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.ts +319 -0
  38. package/ts_web/elements/00group-harness/dees-harness-question-card/index.ts +1 -0
  39. package/ts_web/elements/00group-harness/index.ts +1 -0
  40. package/ts_web/elements/00group-harness/interfaces.ts +36 -0
  41. package/ts_web/elements/00group-harness/toolregistry.ts +8 -0
  42. package/ts_web/elements/00theme.ts +7 -0
@@ -57,6 +57,15 @@ export class DeesHarnessComposer extends DeesElement {
57
57
  @property()
58
58
  accessor placeholder: string = 'Message the agent — drop or paste files to attach…';
59
59
 
60
+ /**
61
+ * Docked composers sit flush against their container edge-to-edge (a
62
+ * top border instead of the floating card chrome) — used by
63
+ * dees-harness-chat so the bar meets the transcript and the tasks panel
64
+ * without a gap.
65
+ */
66
+ @property({ type: Boolean, reflect: true })
67
+ accessor docked: boolean = false;
68
+
60
69
  @property({ type: Boolean })
61
70
  accessor disabled: boolean = false;
62
71
 
@@ -94,6 +103,16 @@ export class DeesHarnessComposer extends DeesElement {
94
103
  @property({ attribute: false })
95
104
  accessor effortOptions: string[] = [];
96
105
 
106
+ /**
107
+ * Agent mode switcher (e.g. Planning / Building). Renders a segmented
108
+ * control when modeOptions is set; emits harness-mode-change.
109
+ */
110
+ @property()
111
+ accessor mode: string = '';
112
+
113
+ @property({ attribute: false })
114
+ accessor modeOptions: string[] = [];
115
+
97
116
  @property({ type: Boolean })
98
117
  accessor showModelControls: boolean = true;
99
118
 
@@ -121,6 +140,19 @@ export class DeesHarnessComposer extends DeesElement {
121
140
  box-shadow: 0 0 0 3px var(--dees-color-focus-ring);
122
141
  }
123
142
 
143
+ :host([docked]) .composer {
144
+ border: 0;
145
+ border-top: 1px solid var(--dees-color-border-subtle);
146
+ border-radius: 0;
147
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md) var(--dees-spacing-md);
148
+ box-shadow: var(--dees-shadow-up-sm);
149
+ }
150
+
151
+ :host([docked]) .composer.dragOver {
152
+ border-top-color: var(--dees-color-accent-primary);
153
+ box-shadow: inset 0 3px 0 0 var(--dees-color-focus-ring);
154
+ }
155
+
124
156
  .fileInput {
125
157
  display: none;
126
158
  }
@@ -214,6 +246,44 @@ export class DeesHarnessComposer extends DeesElement {
214
246
  gap: var(--dees-spacing-sm);
215
247
  }
216
248
 
249
+ /* segmented mode switcher (Planning / Building) */
250
+ .modeSwitch {
251
+ display: inline-flex;
252
+ flex: 0 0 auto;
253
+ padding: 2px;
254
+ border-radius: var(--dees-radius-md);
255
+ corner-shape: var(--dees-corner-shape);
256
+ background: var(--dees-color-fill-secondary);
257
+ }
258
+
259
+ .modeSwitch button {
260
+ height: calc(var(--dees-control-height-sm) - 4px);
261
+ padding: 0 10px;
262
+ border: 0;
263
+ border-radius: calc(var(--dees-radius-md) - 2px);
264
+ corner-shape: var(--dees-corner-shape);
265
+ background: transparent;
266
+ color: var(--dees-color-text-secondary);
267
+ cursor: pointer;
268
+ font-family: inherit;
269
+ font-size: var(--dees-font-control-size-sm, 12px);
270
+ font-weight: 500;
271
+ transition:
272
+ background var(--dees-transition-fast) var(--dees-ease-standard),
273
+ color var(--dees-transition-fast) var(--dees-ease-standard);
274
+ }
275
+
276
+ .modeSwitch button.active {
277
+ background: var(--dees-color-bg-primary);
278
+ color: var(--dees-color-text-primary);
279
+ box-shadow: var(--dees-shadow-xs);
280
+ }
281
+
282
+ .modeSwitch button:disabled {
283
+ cursor: default;
284
+ opacity: 0.55;
285
+ }
286
+
217
287
  .modelControls {
218
288
  display: flex;
219
289
  gap: var(--dees-spacing-xs);
@@ -289,6 +359,26 @@ export class DeesHarnessComposer extends DeesElement {
289
359
  >
290
360
  <dees-icon icon="lucide:Paperclip" iconSize="14"></dees-icon>
291
361
  </dees-button>
362
+ ${this.modeOptions.length
363
+ ? html`
364
+ <div class="modeSwitch" role="radiogroup" aria-label="Agent mode">
365
+ ${this.modeOptions.map((option) => html`
366
+ <button
367
+ type="button"
368
+ role="radio"
369
+ aria-checked=${option === this.mode ? 'true' : 'false'}
370
+ class=${option === this.mode ? 'active' : ''}
371
+ ?disabled=${this.disabled}
372
+ @click=${() => {
373
+ if (this.mode === option) return;
374
+ this.mode = option;
375
+ this.emit('harness-mode-change', { mode: option });
376
+ }}
377
+ >${option}</button>
378
+ `)}
379
+ </div>
380
+ `
381
+ : ''}
292
382
  ${this.showModelControls && (this.modelOptions.length || this.effortOptions.length)
293
383
  ? html`
294
384
  <div class="modelControls">
@@ -391,6 +481,15 @@ export class DeesHarnessComposer extends DeesElement {
391
481
  if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
392
482
  event.preventDefault();
393
483
  this.send();
484
+ return;
485
+ }
486
+ // Shift+Tab cycles the agent mode without leaving the prompt
487
+ if (event.key === 'Tab' && event.shiftKey && this.modeOptions.length) {
488
+ event.preventDefault();
489
+ const currentIndex = this.modeOptions.indexOf(this.mode);
490
+ const nextMode = this.modeOptions[(currentIndex + 1) % this.modeOptions.length];
491
+ this.mode = nextMode;
492
+ this.emit('harness-mode-change', { mode: nextMode });
394
493
  }
395
494
  };
396
495
 
@@ -11,10 +11,12 @@ import {
11
11
  directives,
12
12
  } from '@design.estate/dees-element';
13
13
  import { themeDefaultStyles } from '../../00theme.js';
14
- import type { IHarnessMessage, IHarnessStatus, THarnessStreamDelta } from '../interfaces.js';
14
+ import type { IHarnessMessage, IHarnessPermissionRequest, IHarnessQuestionRequest, IHarnessStatus, THarnessStreamDelta } from '../interfaces.js';
15
15
  import { DeesHarnessMessage } from '../dees-harness-message/dees-harness-message.js';
16
16
  import { DeesHarnessToolRegistry } from '../toolregistry.js';
17
17
  import '../dees-harness-status/dees-harness-status.js';
18
+ import '../dees-harness-permission-card/dees-harness-permission-card.js';
19
+ import '../dees-harness-question-card/dees-harness-question-card.js';
18
20
  import '../../00group-utility/dees-icon/dees-icon.js';
19
21
 
20
22
  const { repeat } = directives;
@@ -27,6 +29,11 @@ declare global {
27
29
 
28
30
  const pinThresholdPx = 48;
29
31
 
32
+ type TTimelineItem =
33
+ | { kind: 'message'; time: number; key: string; message: IHarnessMessage }
34
+ | { kind: 'permission'; time: number; key: string; request: IHarnessPermissionRequest }
35
+ | { kind: 'question'; time: number; key: string; question: IHarnessQuestionRequest };
36
+
30
37
  /**
31
38
  * Scrollable, streaming-safe message log. Messages render through a keyed
32
39
  * repeat (stable DOM per message id); `applyDelta()` routes incremental
@@ -45,6 +52,18 @@ export class DeesHarnessMessageList extends DeesElement {
45
52
  @property({ attribute: false })
46
53
  accessor status: IHarnessStatus = { type: 'idle' };
47
54
 
55
+ /** permission requests interleave chronologically with the messages;
56
+ * answered ones stay visible showing their outcome */
57
+ @property({ attribute: false })
58
+ accessor permissions: IHarnessPermissionRequest[] = [];
59
+
60
+ @property({ type: Boolean })
61
+ accessor permissionsBusy: boolean = false;
62
+
63
+ /** agent questions interleave like permissions; answered ones persist */
64
+ @property({ attribute: false })
65
+ accessor questions: IHarnessQuestionRequest[] = [];
66
+
48
67
  @property({ type: Boolean })
49
68
  accessor autoFollow: boolean = true;
50
69
 
@@ -89,6 +108,14 @@ export class DeesHarnessMessageList extends DeesElement {
89
108
  box-sizing: border-box;
90
109
  }
91
110
 
111
+ /* observed by the follow ResizeObserver; fills the viewport so the
112
+ empty state can center */
113
+ .scrollContent {
114
+ min-height: 100%;
115
+ display: flex;
116
+ flex-direction: column;
117
+ }
118
+
92
119
  .stack {
93
120
  display: block;
94
121
  }
@@ -104,10 +131,10 @@ export class DeesHarnessMessageList extends DeesElement {
104
131
  }
105
132
 
106
133
  .empty {
134
+ flex: 1;
107
135
  display: grid;
108
136
  place-content: center;
109
137
  gap: var(--dees-spacing-xs);
110
- height: 100%;
111
138
  min-height: 120px;
112
139
  text-align: center;
113
140
  }
@@ -149,29 +176,80 @@ export class DeesHarnessMessageList extends DeesElement {
149
176
  `,
150
177
  ];
151
178
 
179
+ /** messages and permissions merged chronologically (stable ids as keys) */
180
+ private get timelineItems(): TTimelineItem[] {
181
+ const items: TTimelineItem[] = this.messages.map((message) => ({
182
+ kind: 'message' as const,
183
+ time: message.createdAt || 0,
184
+ key: message.id,
185
+ message,
186
+ }));
187
+ for (const request of this.permissions) {
188
+ items.push({
189
+ kind: 'permission' as const,
190
+ // pending requests pin to the end (action required); answered ones
191
+ // freeze at their response time in the flow
192
+ time: request.response
193
+ ? (request.respondedAt ?? request.createdAt ?? 0)
194
+ : Number.MAX_SAFE_INTEGER,
195
+ key: `perm:${request.id}`,
196
+ request,
197
+ });
198
+ }
199
+ for (const question of this.questions) {
200
+ items.push({
201
+ kind: 'question' as const,
202
+ time: question.response
203
+ ? (question.respondedAt ?? question.createdAt ?? 0)
204
+ : Number.MAX_SAFE_INTEGER,
205
+ key: `question:${question.id}`,
206
+ question,
207
+ });
208
+ }
209
+ return items.sort((left, right) => left.time - right.time);
210
+ }
211
+
152
212
  public render(): TemplateResult {
213
+ const items = this.timelineItems;
153
214
  return html`
154
- <div class="scroll" @scroll=${this.handleScroll}>
155
- ${this.messages.length
215
+ <div
216
+ class="scroll"
217
+ @scroll=${this.handleScroll}
218
+ @wheel=${this.handleUserScrollIntent}
219
+ @touchmove=${this.handleUserScrollIntent}
220
+ >
221
+ <div class="scrollContent">
222
+ ${items.length
156
223
  ? html`
157
224
  <div class="stack">
158
225
  ${repeat(
159
- this.messages,
160
- (message) => message.id,
161
- (message) => html`
162
- <dees-harness-message
163
- data-message-id=${message.id}
164
- .message=${message}
165
- .showRole=${this.showRoles}
166
- .showTimestamp=${this.showTimestamps}
167
- .toolRegistry=${this.toolRegistry}
168
- .suppressEntry=${!this.entryAnimationsEnabled}
169
- ></dees-harness-message>
170
- `,
226
+ items,
227
+ (item) => item.key,
228
+ (item) => item.kind === 'message'
229
+ ? html`
230
+ <dees-harness-message
231
+ data-message-id=${item.message.id}
232
+ .message=${item.message}
233
+ .showRole=${this.showRoles}
234
+ .showTimestamp=${this.showTimestamps}
235
+ .toolRegistry=${this.toolRegistry}
236
+ .suppressEntry=${!this.entryAnimationsEnabled}
237
+ ></dees-harness-message>
238
+ `
239
+ : item.kind === 'permission'
240
+ ? html`
241
+ <dees-harness-permission-card
242
+ .request=${item.request}
243
+ .busy=${this.permissionsBusy}
244
+ ></dees-harness-permission-card>
245
+ `
246
+ : html`
247
+ <dees-harness-question-card
248
+ .request=${item.question}
249
+ .busy=${this.permissionsBusy}
250
+ ></dees-harness-question-card>
251
+ `,
171
252
  )}
172
- <div class="statusRow">
173
- <dees-harness-status .status=${this.status}></dees-harness-status>
174
- </div>
175
253
  </div>
176
254
  `
177
255
  : html`
@@ -182,6 +260,10 @@ export class DeesHarnessMessageList extends DeesElement {
182
260
  </slot>
183
261
  </div>
184
262
  `}
263
+ <div class="statusRow">
264
+ <dees-harness-status .status=${this.status}></dees-harness-status>
265
+ </div>
266
+ </div>
185
267
  </div>
186
268
  ${this.showJumpChip
187
269
  ? html`
@@ -269,33 +351,53 @@ export class DeesHarnessMessageList extends DeesElement {
269
351
  return container.scrollHeight - container.scrollTop - container.clientHeight < pinThresholdPx;
270
352
  }
271
353
 
272
- private followFrame = 0;
273
- private settleTimeout: ReturnType<typeof setTimeout> | null = null;
354
+ private followUntilTs = 0;
355
+ private lastFollowScrollTop = -1;
356
+ private contentResizeObserver: ResizeObserver | null = null;
274
357
 
358
+ /**
359
+ * One-motion follow: while pinned, content changes open a short follow
360
+ * window (entry animation duration plus margin) and a ResizeObserver on
361
+ * the scroll content re-pins the bottom on every layout change — after
362
+ * layout but before paint, so a growing box and the scroll move together
363
+ * in the same frame. Wheel/touch toward the top, or any external upward
364
+ * scroll (deviation guard), ends the window so it never fights the user.
365
+ */
275
366
  private followAfterContentChange(): void {
276
367
  if (!this.autoFollow) return;
277
368
  if (this.isPinnedToBottom) {
278
- if (!this.followFrame) {
279
- this.followFrame = requestAnimationFrame(() => {
280
- this.followFrame = 0;
281
- this.scrollToBottom(true);
282
- });
283
- }
284
- // entry animations grow content for ~200ms after insertion; follow
285
- // through once they settle (unless the user scrolled far away)
286
- if (this.settleTimeout) clearTimeout(this.settleTimeout);
287
- this.settleTimeout = setTimeout(() => {
288
- this.settleTimeout = null;
289
- const container = this.scrollContainer;
290
- if (!container) return;
291
- const distance = container.scrollHeight - container.scrollTop - container.clientHeight;
292
- if (distance < 400) this.scrollToBottom(true);
293
- }, 250);
369
+ this.followUntilTs = performance.now() + 320;
370
+ this.lastFollowScrollTop = -1;
371
+ this.pinIfFollowing();
294
372
  } else {
295
373
  this.showJumpChip = true;
296
374
  }
297
375
  }
298
376
 
377
+ private pinIfFollowing = (): void => {
378
+ if (performance.now() >= this.followUntilTs) return;
379
+ const container = this.scrollContainer;
380
+ if (!container) return;
381
+ // position moved up since our last pin → someone else scrolled; stop
382
+ // before forcing so following never fights the user
383
+ if (this.lastFollowScrollTop >= 0 && container.scrollTop < this.lastFollowScrollTop - 1) {
384
+ this.stopFollowing();
385
+ return;
386
+ }
387
+ container.scrollTop = container.scrollHeight;
388
+ this.lastFollowScrollTop = container.scrollTop;
389
+ };
390
+
391
+ private stopFollowing(): void {
392
+ this.followUntilTs = 0;
393
+ this.lastFollowScrollTop = -1;
394
+ }
395
+
396
+ private handleUserScrollIntent = (event: Event): void => {
397
+ if (event.type === 'wheel' && (event as WheelEvent).deltaY >= 0) return;
398
+ this.stopFollowing();
399
+ };
400
+
299
401
  public scrollToBottom(force = false): void {
300
402
  const container = this.scrollContainer;
301
403
  if (!container) return;
@@ -313,13 +415,18 @@ export class DeesHarnessMessageList extends DeesElement {
313
415
 
314
416
  public updated(changedProperties: Map<string, any>): void {
315
417
  super.updated(changedProperties);
316
- if (changedProperties.has('messages') || changedProperties.has('status')) {
418
+ if (changedProperties.has('messages') || changedProperties.has('status') || changedProperties.has('permissions') || changedProperties.has('questions')) {
317
419
  this.followAfterContentChange();
318
420
  }
319
421
  }
320
422
 
321
423
  public firstUpdated(changedProperties: Map<string, any>): void {
322
424
  super.firstUpdated(changedProperties);
425
+ const content = this.shadowRoot?.querySelector('.scrollContent');
426
+ if (content) {
427
+ this.contentResizeObserver = new ResizeObserver(this.pinIfFollowing);
428
+ this.contentResizeObserver.observe(content);
429
+ }
323
430
  // let the initial batch paint without entry animations, then enable them
324
431
  requestAnimationFrame(() => {
325
432
  requestAnimationFrame(() => {
@@ -329,14 +436,9 @@ export class DeesHarnessMessageList extends DeesElement {
329
436
  }
330
437
 
331
438
  public async disconnectedCallback(): Promise<void> {
332
- if (this.followFrame) {
333
- cancelAnimationFrame(this.followFrame);
334
- this.followFrame = 0;
335
- }
336
- if (this.settleTimeout) {
337
- clearTimeout(this.settleTimeout);
338
- this.settleTimeout = null;
339
- }
439
+ this.stopFollowing();
440
+ this.contentResizeObserver?.disconnect();
441
+ this.contentResizeObserver = null;
340
442
  await super.disconnectedCallback();
341
443
  }
342
444
  }
@@ -48,9 +48,14 @@ export class DeesHarnessPermissionCard extends DeesElement {
48
48
  @state()
49
49
  accessor remember: boolean = false;
50
50
 
51
+ /** local optimistic answer; request.response (host echo) takes precedence */
51
52
  @state()
52
53
  accessor answered: THarnessPermissionResponse | undefined = undefined;
53
54
 
55
+ public get effectiveResponse(): THarnessPermissionResponse | undefined {
56
+ return this.request.response ?? this.answered;
57
+ }
58
+
54
59
  public static styles = [
55
60
  themeDefaultStyles,
56
61
  cssManager.defaultStyles,
@@ -157,7 +162,7 @@ export class DeesHarnessPermissionCard extends DeesElement {
157
162
 
158
163
  public render(): TemplateResult {
159
164
  const request = this.request;
160
- const disabled = this.busy || this.answered !== undefined;
165
+ const disabled = this.busy || this.effectiveResponse !== undefined;
161
166
  const metadataEntries = Object.entries(request.metadata ?? {});
162
167
  return html`
163
168
  <div class="card">
@@ -177,8 +182,8 @@ export class DeesHarnessPermissionCard extends DeesElement {
177
182
  `
178
183
  : ''}
179
184
  <div class="actions">
180
- ${this.answered
181
- ? html`<span class="answeredNote">Answered: ${this.answered}${this.remember ? ' (remembered)' : ''}</span>`
185
+ ${this.effectiveResponse
186
+ ? html`<span class="answeredNote">Answered: ${this.effectiveResponse}${this.remember ? ' (remembered)' : ''}</span>`
182
187
  : html`
183
188
  <dees-button type="accent" size="sm" ?disabled=${disabled} @clicked=${() => this.respond('once')}>
184
189
  Allow Once
@@ -207,7 +212,7 @@ export class DeesHarnessPermissionCard extends DeesElement {
207
212
  }
208
213
 
209
214
  private respond(response: THarnessPermissionResponse): void {
210
- if (this.busy || this.answered !== undefined) return;
215
+ if (this.busy || this.effectiveResponse !== undefined) return;
211
216
  this.answered = response;
212
217
  this.dispatchEvent(
213
218
  new CustomEvent('harness-permission-response', {
@@ -0,0 +1,70 @@
1
+ import { html } from '@design.estate/dees-element';
2
+
3
+ const logResponse = (event: CustomEvent) => {
4
+ console.log('harness-question-response', event.detail);
5
+ };
6
+
7
+ export const demoFunc = () => html`
8
+ <style>
9
+ .questionDemo {
10
+ display: grid;
11
+ gap: 16px;
12
+ max-width: 640px;
13
+ margin: 40px auto;
14
+ padding: 24px;
15
+ border-radius: var(--dees-radius-xl);
16
+ background: var(--dees-color-bg-primary);
17
+ }
18
+ </style>
19
+ <div class="questionDemo" @harness-question-response=${logResponse}>
20
+ <dees-harness-question-card
21
+ .request=${{
22
+ id: 'q1',
23
+ question: 'Which database should the new service use?',
24
+ createdAt: Date.now(),
25
+ options: [
26
+ { label: 'PostgreSQL', description: 'Relational, already in the stack' },
27
+ { label: 'MongoDB', description: 'Document store, flexible schema' },
28
+ { label: 'SQLite', description: 'Embedded, zero ops' },
29
+ ],
30
+ }}
31
+ ></dees-harness-question-card>
32
+ <dees-harness-question-card
33
+ .request=${{
34
+ id: 'q2',
35
+ question: 'Which environments should the rollout include?',
36
+ multiSelect: true,
37
+ allowCustom: false,
38
+ options: [
39
+ { label: 'staging' },
40
+ { label: 'production-eu' },
41
+ { label: 'production-us' },
42
+ ],
43
+ }}
44
+ ></dees-harness-question-card>
45
+ <dees-harness-question-card
46
+ .request=${{
47
+ id: 'q3',
48
+ question: 'Anything else I should know before migrating?',
49
+ options: [],
50
+ }}
51
+ ></dees-harness-question-card>
52
+ <dees-harness-question-card
53
+ busy
54
+ .request=${{
55
+ id: 'q-busy',
56
+ question: 'Waiting for the runtime — which region?',
57
+ options: [{ label: 'eu-central' }, { label: 'us-east' }],
58
+ }}
59
+ ></dees-harness-question-card>
60
+ <dees-harness-question-card
61
+ .request=${{
62
+ id: 'q4',
63
+ question: 'Which framework for the admin UI?',
64
+ options: [{ label: 'Lit' }, { label: 'React' }],
65
+ response: ['Lit'],
66
+ respondedAt: Date.now() - 60_000,
67
+ }}
68
+ ></dees-harness-question-card>
69
+ </div>
70
+ `;