@design.estate/dees-catalog 3.93.0 → 3.95.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 (63) hide show
  1. package/dist_bundle/bundle.js +2868 -1376
  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 +6 -3
  4. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +34 -4
  5. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +49 -45
  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/00group-layout/dees-mosaic/dees-mosaic.d.ts +49 -0
  24. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.d.ts +1 -0
  25. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.js +82 -0
  26. package/dist_ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.js +622 -0
  27. package/dist_ts_web/elements/00group-layout/dees-mosaic/index.d.ts +3 -0
  28. package/dist_ts_web/elements/00group-layout/dees-mosaic/index.js +4 -0
  29. package/dist_ts_web/elements/00group-layout/dees-mosaic/interaction.d.ts +37 -0
  30. package/dist_ts_web/elements/00group-layout/dees-mosaic/interaction.js +174 -0
  31. package/dist_ts_web/elements/00group-layout/dees-mosaic/tree.d.ts +33 -0
  32. package/dist_ts_web/elements/00group-layout/dees-mosaic/tree.js +199 -0
  33. package/dist_ts_web/elements/00group-layout/dees-mosaic/types.d.ts +52 -0
  34. package/dist_ts_web/elements/00group-layout/dees-mosaic/types.js +8 -0
  35. package/dist_ts_web/elements/00group-layout/index.d.ts +1 -0
  36. package/dist_ts_web/elements/00group-layout/index.js +2 -1
  37. package/dist_ts_web/elements/00theme.js +6 -1
  38. package/dist_watch/bundle.js +2869 -1377
  39. package/dist_watch/bundle.js.map +4 -4
  40. package/package.json +1 -1
  41. package/readme.md +41 -6
  42. package/readme.playbook.md +1 -1
  43. package/ts_web/00_commitinfo_data.ts +1 -1
  44. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +37 -3
  45. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +31 -44
  46. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +4 -1
  47. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +99 -0
  48. package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +148 -46
  49. package/ts_web/elements/00group-harness/dees-harness-permission-card/dees-harness-permission-card.ts +9 -4
  50. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.demo.ts +70 -0
  51. package/ts_web/elements/00group-harness/dees-harness-question-card/dees-harness-question-card.ts +319 -0
  52. package/ts_web/elements/00group-harness/dees-harness-question-card/index.ts +1 -0
  53. package/ts_web/elements/00group-harness/index.ts +1 -0
  54. package/ts_web/elements/00group-harness/interfaces.ts +36 -0
  55. package/ts_web/elements/00group-harness/toolregistry.ts +8 -0
  56. package/ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.demo.ts +87 -0
  57. package/ts_web/elements/00group-layout/dees-mosaic/dees-mosaic.ts +562 -0
  58. package/ts_web/elements/00group-layout/dees-mosaic/index.ts +3 -0
  59. package/ts_web/elements/00group-layout/dees-mosaic/interaction.ts +187 -0
  60. package/ts_web/elements/00group-layout/dees-mosaic/tree.ts +239 -0
  61. package/ts_web/elements/00group-layout/dees-mosaic/types.ts +63 -0
  62. package/ts_web/elements/00group-layout/index.ts +1 -0
  63. package/ts_web/elements/00theme.ts +7 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-catalog",
3
- "version": "3.93.0",
3
+ "version": "3.95.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
@@ -57,14 +57,14 @@ For developers working on this library, please refer to the [UI Components Playb
57
57
  |----------|------------|
58
58
  | **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols), [`DeesActionbar`](#deesactionbar) |
59
59
  | **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputToggle`](#deesinputtoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesInputCode`](#deesinputcode), [`DeesFormSubmit`](#deesformsubmit) |
60
- | **App Shell (Layout)** | [`DeesAppui`](#deesappui-️), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiBottombar`](#deesappuibottombar), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
60
+ | **App Shell (Layout)** | [`DeesMosaic`](#deesmosaic), [`DeesAppui`](#deesappui-️), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiBottombar`](#deesappuibottombar), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
61
61
  | **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination), [`DeesStorageBrowser`](#deesstorgebrowser) |
62
62
  | **Media & Thumbnails** | [`DeesThumbnailPdf`](#deesthumbnailpdf), [`DeesThumbnailImage`](#deesthumbnailimage), [`DeesThumbnailAudio`](#deesthumbnailaudio), [`DeesThumbnailVideo`](#deesthumbnalvideo), [`DeesThumbnailNote`](#deesthumbnailnote), [`DeesThumbnailFolder`](#deesthumbnailfolder), [`DeesPreview`](#deespreview), [`DeesPdfViewer`](#deespdfviewer), [`DeesImageViewer`](#deesimageviewer), [`DeesAudioViewer`](#deesaudioviewer), [`DeesVideoViewer`](#deesvideoviewer) |
63
63
  | **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartBar`](#deeschartbar), [`DeesChartDonut`](#deeschartdonut), [`DeesChartGauge`](#deeschartgauge), [`DeesChartRadar`](#deeschartradar), [`DeesChartLog`](#deeschartlog) |
64
64
  | **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
65
65
  | **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
66
66
  | **Workspace / IDE** | [`DeesWorkspace`](#deesworkspace), [`DeesWorkspaceMonaco`](#deesworkspacemonaco), [`DeesWorkspaceDiffEditor`](#deesworkspacediffeditor), [`DeesWorkspaceFiletree`](#deesworkspacefiletree), [`DeesWorkspaceTerminal`](#deesworkspaceterminal), [`DeesWorkspaceTerminalPreview`](#deesworkspaceterminalpreview), [`DeesWorkspaceMarkdown`](#deesworkspacemarkdown), [`DeesWorkspaceMarkdownoutlet`](#deesworkspacemarkdownoutlet), [`DeesWorkspaceBottombar`](#deesworkspacebottombar) |
67
- | **Agentic Chat** | [`DeesHarnessChat`](#deesharnesschat), [`DeesHarnessMessageList`](#deesharnessmessagelist), [`DeesHarnessMessage`](#deesharnessmessage), [`DeesHarnessReasoning`](#deesharnessreasoning), [`DeesHarnessToolCard`](#deesharnesstoolcard), [`DeesHarnessContentBlocks`](#deesharnesscontentblocks), [`DeesHarnessPermissionCard`](#deesharnesspermissioncard), [`DeesHarnessComposer`](#deesharnesscomposer), [`DeesHarnessStatus`](#deesharnessstatus), [`DeesHarnessTodos`](#deesharnesstodos), [`DeesHarnessUsage`](#deesharnessusage), [`DeesHarnessSessionList`](#deesharnesssessionlist) |
67
+ | **Agentic Chat** | [`DeesHarnessChat`](#deesharnesschat), [`DeesHarnessMessageList`](#deesharnessmessagelist), [`DeesHarnessMessage`](#deesharnessmessage), [`DeesHarnessReasoning`](#deesharnessreasoning), [`DeesHarnessToolCard`](#deesharnesstoolcard), [`DeesHarnessContentBlocks`](#deesharnesscontentblocks), [`DeesHarnessPermissionCard`](#deesharnesspermissioncard), [`DeesHarnessQuestionCard`](#deesharnessquestioncard), [`DeesHarnessComposer`](#deesharnesscomposer), [`DeesHarnessStatus`](#deesharnessstatus), [`DeesHarnessTodos`](#deesharnesstodos), [`DeesHarnessUsage`](#deesharnessusage), [`DeesHarnessSessionList`](#deesharnesssessionlist) |
68
68
  | **Theming** | [`DeesTheme`](#deestheme), [`DeesUpdater`](#deesupdater) |
69
69
  | **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
70
70
  | **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
@@ -713,6 +713,33 @@ Submit button component specifically designed for `DeesForm`.
713
713
 
714
714
  ### App Shell (Layout) Components
715
715
 
716
+ #### `DeesMosaic`
717
+
718
+ Sway-like tiling workspace: a controlled split-tree layout where tiles resize against each other, drag by a slim tilebar (which also closes them), and dock onto any edge of another tile with a live drop preview. Content projects through named slots, so hosts keep light-DOM ownership — Electron `<webview>` content survives every layout change because slot reassignment never moves the light-DOM node.
719
+
720
+ ```typescript
721
+ import { createMosaicLeaf, mosaicInsert, type IMosaicLayout } from '@design.estate/dees-catalog';
722
+
723
+ let layout: IMosaicLayout = mosaicInsert({ root: null }, null, 'right', createMosaicLeaf('editor', 'l-editor'));
724
+ layout = mosaicInsert(layout, 'l-editor', 'right', createMosaicLeaf('chat', 'l-chat'));
725
+ layout = mosaicInsert(layout, null, 'bottom', createMosaicLeaf('terminal', 'l-term'));
726
+ ```
727
+
728
+ ```html
729
+ <dees-mosaic
730
+ .layout=${layout}
731
+ .surfaces=${{ editor: { title: 'Editor', icon: 'lucide:Code' }, chat: { title: 'AI Chat' }, terminal: { title: 'Terminal' } }}
732
+ @mosaic-layout-change=${(e) => { layout = e.detail.layout; /* persist */ }}
733
+ >
734
+ <my-editor slot="tile-l-editor"></my-editor>
735
+ <my-chat slot="tile-l-chat"></my-chat>
736
+ <my-terminal slot="tile-l-term"></my-terminal>
737
+ <div slot="empty">Workspace is empty.</div>
738
+ </dees-mosaic>
739
+ ```
740
+
741
+ The component is controlled: apply `e.detail.layout` back (and persist it) on every `mosaic-layout-change` (`reason: 'resize' | 'drop' | 'close' | 'equalize'`). Other events: cancelable `mosaic-tile-close`, `mosaic-tile-focus`, throttled `mosaic-resizing` (re-measure embedded content), `mosaic-drag-state`. Methods: `getLeafRects()` (viewport rect per leaf content area), `equalize(splitId?)`, `focusTile(id)`. Pure helpers (`mosaicInsert/mosaicRemove/mosaicMove/mosaicResize/mosaicEqualize/mosaicNormalize/mosaicLeaves`) are exported for host-side layout logic. Splitters are keyboard-accessible (`role="separator"`, arrow keys resize, double-click equalizes); Escape cancels drags.
742
+
716
743
  #### `DeesAppui` 🏗️
717
744
  A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, bottom bar, and view management.
718
745
 
@@ -1699,7 +1726,7 @@ The `00group-harness` family renders what an agent harness produces — streamin
1699
1726
 
1700
1727
  #### `DeesHarnessChat`
1701
1728
 
1702
- The assembled chat: optional toolbar (heading, usage chip, history/new-session buttons), streaming message list, pinned permission queue, status line, and composer.
1729
+ The assembled chat: optional toolbar (heading, usage chip, history/new-session buttons), streaming message list with inline permission cards, transcript status line, and docked composer.
1703
1730
 
1704
1731
  ```typescript
1705
1732
  const chat = document.querySelector('dees-harness-chat');
@@ -1721,13 +1748,13 @@ chat.applyDelta({ type: 'text', messageId: 'm1', delta: 'chunk' });
1721
1748
  chat.applyDelta({ type: 'message-end', messageId: 'm1', usage: { totalTokens: 1200 } });
1722
1749
  ```
1723
1750
 
1724
- Key props: `messages`, `permissions`, `status`, `usage`, `todos`/`showTodosPanel`, `heading`, `subheading`, `showToolbar`, `busy`, `queuedCount`, `disabled`, `model`/`modelOptions`, `reasoningEffort`/`effortOptions`, `toolRegistry`.
1751
+ Key props: `messages`, `permissions`, `questions`, `status`, `usage`, `todos`/`showTodosPanel`, `heading`, `subheading`, `showToolbar`, `busy`, `queuedCount`, `disabled`, `model`/`modelOptions`, `reasoningEffort`/`effortOptions`, `mode`/`modeOptions`, `toolRegistry`. The status line renders inside the transcript column (it never pushes between the Tasks panel and the composer), and both the toolbar and the docked composer cast a subtle shadow over the scrolling messages.
1725
1752
 
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`.
1753
+ 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-mode-change`, `harness-permission-response`, `harness-question-response`, `harness-new-session`, `harness-open-sessions`.
1727
1754
 
1728
1755
  #### `DeesHarnessMessageList`
1729
1756
 
1730
- Scrollable streaming-safe log. Keyed repeat by `message.id` keeps DOM identity stable; `applyDelta()` routes updates to the affected message element only. Auto-follows the bottom while pinned; otherwise raises a "Jump to latest" chip instead of yanking the scroll. Props: `messages`, `status`, `autoFollow`, `showRoles`, `showTimestamps`, `emptyTitle`/`emptyText` (+ `slot="empty"`).
1757
+ Scrollable streaming-safe log. Keyed repeat keeps DOM identity stable; `applyDelta()` routes updates to the affected message element only. Auto-follows the bottom while pinned; otherwise raises a "Jump to latest" chip instead of yanking the scroll. Permission requests and agent questions render inline in the flow, and while pinned the list follows content growth frame-by-frame (ResizeObserver), so a new box growing into place and the scroll move as one motion. Props: `messages`, `permissions`/`permissionsBusy`, `questions`, `status`, `autoFollow`, `showRoles`, `showTimestamps`, `emptyTitle`/`emptyText` (+ `slot="empty"`).
1731
1758
 
1732
1759
  #### `DeesHarnessMessage`
1733
1760
 
@@ -1764,12 +1791,20 @@ Renders MCP `CallToolResult` content structurally: text, base64 images, audio wi
1764
1791
 
1765
1792
  Permission prompt: title, type badge (shell/delete → error tone, write/browser → warning), metadata key/values, Allow Once / Always Allow / Reject, and a "remember" checkbox (`always` implies remember). Emits `harness-permission-response`; disables itself once answered or while `busy`.
1766
1793
 
1794
+ Inside the chat, permission cards render **inline with the messages**: pending requests pin to the end of the transcript (action required), and answered ones freeze at their response time in the flow. Set `response`/`respondedAt` on the request after handling it and keep it in the array — the card stays in the transcript showing what was granted or rejected.
1795
+
1796
+ #### `DeesHarnessQuestionCard`
1797
+
1798
+ A question the agent asks mid-run: option buttons with descriptions (single-select answers on click; `multiSelect` collects then confirms) plus an optional free-text answer (`allowCustom`, default on — Enter or "Send Answer" submits, combining selections with the text). Emits `harness-question-response` with `{ requestId, answers, request }`. Questions flow through `dees-harness-chat`'s `questions` property and render inline like permissions: pending ones pin to the end of the transcript, and answered ones persist showing the chosen answers once the host sets `response`/`respondedAt`.
1799
+
1767
1800
  #### `DeesHarnessComposer`
1768
1801
 
1769
1802
  Prompt input: autogrow textarea, attachment picker/paste/drag-drop with per-file (10 MB) and total (30 MB) limits and MIME allowlist, optional model + reasoning-effort dropdowns, Cmd/Ctrl+Enter to send. Controlled component — the host owns `value` and `attachments`.
1770
1803
 
1771
1804
  While `busy`, Stop renders as an additional button and Send stays available so hosts can queue messages. With an empty prompt and `queuedCount > 0`, the primary action becomes **Steer now** (`harness-steer` event) to force the agent to continue with the queued messages immediately.
1772
1805
 
1806
+ Set `modeOptions` (e.g. `['Planning', 'Building']`) to render a segmented **mode switcher**; the active `mode` changes on click — or **Shift+Tab** inside the prompt cycles it — and emits `harness-mode-change`. Inside `dees-harness-chat` the composer renders `docked` — flush edge-to-edge with a top border and an upward shadow (`--dees-shadow-up-sm`) so it meets the transcript and the Tasks panel like the toolbar does; standalone composers keep the floating-card chrome.
1807
+
1773
1808
  #### `DeesHarnessTodos`
1774
1809
 
1775
1810
  Agent task list for `IHarnessTodoItem[]` — completed items dim with a check and strikethrough, the in-progress item leads with a pulsing accent marker, and an optional heading shows `completed/total` progress. Used automatically as the todo tool-card body (`harnessParseTodos` normalizes tool input shapes) and standalone for a "current tasks" sidebar panel fed from the latest `todowrite` call.
@@ -439,7 +439,7 @@ Components that render continuously arriving data (agent chat, logs) follow the
439
439
  - **Props are the source of truth; deltas are the fast path.** The host owns the array (e.g. `.messages`). Incremental updates go through `applyDelta()`, which mutates the same object instance held in the array and calls `requestUpdate()` on the one affected child element. A later wholesale re-assignment of the array is therefore idempotent — no divergence between streamed and re-set state.
440
440
  - **Keyed `repeat` for identity.** List children render via `directives.repeat` keyed by a stable id so streaming never recreates sibling DOM (which would drop scroll position, expansion state, and selection).
441
441
  - **Cheap while streaming, rich when done.** While `streaming` is set, text renders as pre-wrap with a cursor; expensive work (markdown parse) is throttled (≥150 ms, token-guarded like the codebox highlight pattern) and syntax highlighting waits for the final parse.
442
- - **Scroll follow with user override.** Auto-scroll only when the user is already pinned to the bottom (`scrollHeight - scrollTop - clientHeight < threshold`); otherwise show a "jump to latest" chip. Schedule follow-scrolls through a single pending `requestAnimationFrame`, cancelled in `disconnectedCallback`.
442
+ - **Scroll follow with user override.** Auto-scroll only when the user is already pinned to the bottom (`scrollHeight - scrollTop - clientHeight < threshold`); otherwise show a "jump to latest" chip. While pinned, a content change opens a short follow window and a `ResizeObserver` on the scroll content re-pins the bottom on every layout change (post-layout, pre-paint), so a growing box and the scroll move as one motion; an external upward scroll ends the window before the next pin so following never fights the user. Disconnect the observer in `disconnectedCallback`.
443
443
  - **Interaction state lives on the child.** Expand/collapse (reasoning, tool cards) is element-local state seeded from data (`expanded ?? isStreaming`), so parent re-renders can't reset what the user opened.
444
444
 
445
445
  ## Performance Optimization
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '3.93.0',
6
+ version: '3.95.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
  }
@@ -223,6 +223,23 @@ const simulate = (event: MouseEvent) => {
223
223
  });
224
224
  };
225
225
 
226
+ const askQuestion = (event: MouseEvent) => {
227
+ const chat = chatOf(event);
228
+ if (!chat) return;
229
+ chat.questions = [
230
+ ...chat.questions,
231
+ {
232
+ id: `question-${Date.now()}`,
233
+ question: 'Which cache-key strategy should I roll out?',
234
+ createdAt: Date.now(),
235
+ options: [
236
+ { label: 'Route manifest hash', description: 'Deterministic, invalidates on route changes' },
237
+ { label: 'Content hash', description: 'Fine-grained, more churn' },
238
+ ],
239
+ },
240
+ ];
241
+ };
242
+
226
243
  const askPermission = (event: MouseEvent) => {
227
244
  const chat = chatOf(event);
228
245
  if (!chat) return;
@@ -274,9 +291,23 @@ export const demoFunc = () => html`
274
291
  console.log('harness-permission-response', event.detail);
275
292
  const chat = (event.target as HTMLElement).closest('.chatDemo')?.querySelector('dees-harness-chat');
276
293
  if (chat) {
277
- setTimeout(() => {
278
- chat.permissions = chat.permissions.filter((request) => request.id !== event.detail.requestId);
279
- }, 600);
294
+ // answered cards stay in the transcript showing the outcome
295
+ chat.permissions = chat.permissions.map((request) =>
296
+ request.id === event.detail.requestId
297
+ ? { ...request, response: event.detail.response, respondedAt: Date.now() }
298
+ : request,
299
+ );
300
+ }
301
+ }}
302
+ @harness-question-response=${(event: CustomEvent) => {
303
+ console.log('harness-question-response', event.detail);
304
+ const chat = (event.target as HTMLElement).closest('.chatDemo')?.querySelector('dees-harness-chat');
305
+ if (chat) {
306
+ chat.questions = chat.questions.map((request) =>
307
+ request.id === event.detail.requestId
308
+ ? { ...request, response: event.detail.answers, respondedAt: Date.now() }
309
+ : request,
310
+ );
280
311
  }
281
312
  }}
282
313
  @harness-new-session=${() => console.log('harness-new-session')}
@@ -289,6 +320,7 @@ export const demoFunc = () => html`
289
320
  <div class="controls">
290
321
  <dees-button type="accent" @clicked=${simulate}>Simulate streaming</dees-button>
291
322
  <dees-button @clicked=${askPermission}>Ask permission</dees-button>
323
+ <dees-button @clicked=${askQuestion}>Ask a question</dees-button>
292
324
  <dees-button @clicked=${toggleError}>Toggle error status</dees-button>
293
325
  <dees-button type="ghost" @clicked=${resetChat}>Reset</dees-button>
294
326
  </div>
@@ -297,6 +329,8 @@ export const demoFunc = () => html`
297
329
  subheading="acme-app · main · connected"
298
330
  model="gpt-5.5"
299
331
  reasoningEffort="high"
332
+ mode="Building"
333
+ .modeOptions=${['Planning', 'Building']}
300
334
  .modelOptions=${['gpt-5.5', 'gpt-5', 'o4-mini']}
301
335
  .effortOptions=${['xhigh', 'high', 'medium', 'low']}
302
336
  .usage=${{ inputTokens: 18_400, outputTokens: 3_100, totalTokens: 21_500 }}
@@ -13,6 +13,7 @@ import type {
13
13
  IHarnessAttachment,
14
14
  IHarnessMessage,
15
15
  IHarnessPermissionRequest,
16
+ IHarnessQuestionRequest,
16
17
  IHarnessStatus,
17
18
  IHarnessTodoItem,
18
19
  IHarnessUsage,
@@ -22,8 +23,6 @@ import { harnessParseTodos } from '../harness.helpers.js';
22
23
  import { DeesHarnessToolRegistry } from '../toolregistry.js';
23
24
  import { DeesHarnessMessageList } from '../dees-harness-message-list/dees-harness-message-list.js';
24
25
  import { DeesHarnessComposer } from '../dees-harness-composer/dees-harness-composer.js';
25
- import '../dees-harness-permission-card/dees-harness-permission-card.js';
26
- import '../dees-harness-status/dees-harness-status.js';
27
26
  import '../dees-harness-todos/dees-harness-todos.js';
28
27
  import '../dees-harness-usage/dees-harness-usage.js';
29
28
  import '../../00group-button/dees-button/dees-button.js';
@@ -35,9 +34,6 @@ declare global {
35
34
  }
36
35
  }
37
36
 
38
- // stable idle binding for the embedded list — a fresh object literal per
39
- // render would re-trigger its follow logic on every chat render
40
- const idleStatus: IHarnessStatus = { type: 'idle' };
41
37
 
42
38
  /**
43
39
  * The assembled agent chat: optional toolbar, streaming message list,
@@ -58,6 +54,10 @@ export class DeesHarnessChat extends DeesElement {
58
54
  @property({ attribute: false })
59
55
  accessor permissions: IHarnessPermissionRequest[] = [];
60
56
 
57
+ /** agent questions render inline like permissions; answered ones persist */
58
+ @property({ attribute: false })
59
+ accessor questions: IHarnessQuestionRequest[] = [];
60
+
61
61
  @property({ attribute: false })
62
62
  accessor status: IHarnessStatus = { type: 'idle' };
63
63
 
@@ -115,6 +115,13 @@ export class DeesHarnessChat extends DeesElement {
115
115
  @property({ attribute: false })
116
116
  accessor effortOptions: string[] = [];
117
117
 
118
+ /** agent mode switcher (e.g. Planning / Building); empty hides it */
119
+ @property()
120
+ accessor mode: string = '';
121
+
122
+ @property({ attribute: false })
123
+ accessor modeOptions: string[] = [];
124
+
118
125
  @property({ attribute: false })
119
126
  accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
120
127
 
@@ -166,6 +173,10 @@ export class DeesHarnessChat extends DeesElement {
166
173
  padding: var(--dees-spacing-sm) var(--dees-spacing-md);
167
174
  border-bottom: 1px solid var(--dees-color-border-subtle);
168
175
  background: var(--dees-color-bg-secondary);
176
+ /* chrome floats above the scrolling transcript */
177
+ position: relative;
178
+ z-index: 1;
179
+ box-shadow: var(--dees-shadow-sm);
169
180
  }
170
181
 
171
182
  .headingBlock {
@@ -203,32 +214,13 @@ export class DeesHarnessChat extends DeesElement {
203
214
  min-height: 0;
204
215
  }
205
216
 
206
- .permissionQueue {
207
- flex: 0 0 auto;
208
- display: block;
209
- padding: 0 var(--dees-spacing-md);
210
- max-height: 40%;
211
- overflow-y: auto;
212
- }
213
-
214
- .permissionQueue > * + * {
215
- margin-top: var(--dees-spacing-xs);
216
- }
217
-
218
- /* always mounted so the status leave transition can play; the status
219
- element renders empty when idle, collapsing the row to zero height */
220
- .statusRow {
221
- flex: 0 0 auto;
222
- padding: 0 var(--dees-spacing-md);
223
- }
224
-
225
- .statusRow dees-harness-status {
226
- margin: 0;
227
- }
228
-
217
+ /* the composer docks edge-to-edge like the toolbar, so the tasks
218
+ panel's border meets its top border cleanly; z-order keeps its
219
+ upward shadow above the transcript */
229
220
  .composerRow {
230
221
  flex: 0 0 auto;
231
- padding: var(--dees-spacing-sm) var(--dees-spacing-md) var(--dees-spacing-md);
222
+ position: relative;
223
+ z-index: 1;
232
224
  }
233
225
  `,
234
226
  ];
@@ -285,7 +277,10 @@ export class DeesHarnessChat extends DeesElement {
285
277
  <div class="chatBody">
286
278
  <dees-harness-message-list
287
279
  .messages=${this.messages}
288
- .status=${idleStatus}
280
+ .permissions=${this.permissions}
281
+ .permissionsBusy=${this.disabled}
282
+ .questions=${this.questions}
283
+ .status=${this.status}
289
284
  .showRoles=${this.showRoles}
290
285
  .toolRegistry=${this.toolRegistry}
291
286
  ></dees-harness-message-list>
@@ -297,22 +292,9 @@ export class DeesHarnessChat extends DeesElement {
297
292
  `
298
293
  : ''}
299
294
  </div>
300
- ${this.permissions.length
301
- ? html`
302
- <div class="permissionQueue">
303
- ${this.permissions.map(
304
- (request) => html`
305
- <dees-harness-permission-card .request=${request} .busy=${this.disabled}></dees-harness-permission-card>
306
- `,
307
- )}
308
- </div>
309
- `
310
- : ''}
311
- <div class="statusRow">
312
- <dees-harness-status .status=${this.status}></dees-harness-status>
313
- </div>
314
295
  <div class="composerRow">
315
296
  <dees-harness-composer
297
+ docked
316
298
  .value=${this.composerValue}
317
299
  .attachments=${this.attachments}
318
300
  .disabled=${this.disabled}
@@ -322,6 +304,8 @@ export class DeesHarnessChat extends DeesElement {
322
304
  .modelOptions=${this.modelOptions}
323
305
  .reasoningEffort=${this.reasoningEffort}
324
306
  .effortOptions=${this.effortOptions}
307
+ .mode=${this.mode}
308
+ .modeOptions=${this.modeOptions}
325
309
  @harness-input=${(event: CustomEvent<{ value: string }>) => {
326
310
  this.composerValue = event.detail?.value ?? '';
327
311
  }}
@@ -332,6 +316,9 @@ export class DeesHarnessChat extends DeesElement {
332
316
  if (event.detail?.model) this.model = event.detail.model;
333
317
  if (event.detail?.reasoningEffort) this.reasoningEffort = event.detail.reasoningEffort;
334
318
  }}
319
+ @harness-mode-change=${(event: CustomEvent<{ mode?: string }>) => {
320
+ if (event.detail?.mode) this.mode = event.detail.mode;
321
+ }}
335
322
  ></dees-harness-composer>
336
323
  </div>
337
324
  `;
@@ -30,11 +30,14 @@ export const demoFunc = () => html`
30
30
  @harness-attachments-change=${logEvent}
31
31
  @harness-attachment-error=${logEvent}
32
32
  @harness-model-change=${logEvent}
33
+ @harness-mode-change=${logEvent}
33
34
  >
34
- <div class="label">default with model controls (events log to console)</div>
35
+ <div class="label">default with mode switcher + model controls (events log to console)</div>
35
36
  <dees-harness-composer
36
37
  model="gpt-5.5"
37
38
  reasoningEffort="high"
39
+ mode="Planning"
40
+ .modeOptions=${['Planning', 'Building']}
38
41
  .modelOptions=${['gpt-5.5', 'gpt-5', 'o4-mini']}
39
42
  .effortOptions=${['xhigh', 'high', 'medium', 'low']}
40
43
  ></dees-harness-composer>
@@ -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