@design.estate/dees-catalog 11.2.1 → 12.0.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 (28) hide show
  1. package/dist_bundle/bundle.js +1927 -1305
  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-conversation-picker/dees-harness-conversation-picker.d.ts +62 -0
  5. package/dist_ts_web/elements/00group-harness/dees-harness-conversation-picker/dees-harness-conversation-picker.demo.d.ts +1 -0
  6. package/dist_ts_web/elements/00group-harness/dees-harness-conversation-picker/dees-harness-conversation-picker.demo.js +38 -0
  7. package/dist_ts_web/elements/00group-harness/dees-harness-conversation-picker/dees-harness-conversation-picker.js +455 -0
  8. package/dist_ts_web/elements/00group-harness/dees-harness-conversation-picker/index.d.ts +1 -0
  9. package/dist_ts_web/elements/00group-harness/dees-harness-conversation-picker/index.js +2 -0
  10. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.d.ts +39 -10
  11. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.js +81 -27
  12. package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +327 -115
  13. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +6 -2
  14. package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +68 -17
  15. package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
  16. package/dist_ts_web/elements/00group-harness/index.js +2 -1
  17. package/dist_ts_web/elements/00group-harness/interfaces.d.ts +35 -16
  18. package/package.json +2 -2
  19. package/readme.md +56 -24
  20. package/ts_web/00_commitinfo_data.ts +1 -1
  21. package/ts_web/elements/00group-harness/dees-harness-conversation-picker/dees-harness-conversation-picker.demo.ts +46 -0
  22. package/ts_web/elements/00group-harness/dees-harness-conversation-picker/dees-harness-conversation-picker.ts +420 -0
  23. package/ts_web/elements/00group-harness/dees-harness-conversation-picker/index.ts +1 -0
  24. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.demo.ts +94 -36
  25. package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +357 -120
  26. package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +67 -16
  27. package/ts_web/elements/00group-harness/index.ts +1 -0
  28. package/ts_web/elements/00group-harness/interfaces.ts +41 -16
package/readme.md CHANGED
@@ -165,6 +165,7 @@ task guide and examples deliberately; the generated table does not replace them.
165
165
  | `dees-harness-chat` | harness | [DeesHarnessChat](#deesharnesschat) | [Source](ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts) |
166
166
  | `dees-harness-composer` | harness | [DeesHarnessComposer](#deesharnesscomposer) | [Source](ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts) |
167
167
  | `dees-harness-content-blocks` | harness | [DeesHarnessContentBlocks](#deesharnesscontentblocks) | [Source](ts_web/elements/00group-harness/dees-harness-content-blocks/dees-harness-content-blocks.ts) |
168
+ | `dees-harness-conversation-picker` | harness | [DeesHarnessConversationPicker](#deesharnessconversationpicker) | [Source](ts_web/elements/00group-harness/dees-harness-conversation-picker/dees-harness-conversation-picker.ts) |
168
169
  | `dees-harness-message` | harness | [DeesHarnessMessage](#deesharnessmessage) | [Source](ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts) |
169
170
  | `dees-harness-message-list` | harness | [DeesHarnessMessageList](#deesharnessmessagelist) | [Source](ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts) |
170
171
  | `dees-harness-overflow-text` | harness | [DeesHarnessOverflowText](#deesharnessoverflowtext) | [Source](ts_web/elements/00group-harness/dees-harness-overflow-text.ts) |
@@ -2640,7 +2641,7 @@ Scrollable streaming-safe log. Up to 40 canonical entries (`messages` + `permiss
2640
2641
 
2641
2642
  `refreshMessages(ids?)` reconciles in-place host mutations without replacing canonical objects. With IDs, it refreshes only matching message rows that are currently mounted; matching offscreen rows remain unmounted and use the mutated canonical objects when later mounted. With IDs omitted, it rebuilds structural transcript state and refreshes all mounted rows. `[]` is a no-op.
2642
2643
 
2643
- Producers with authoritative source chronology can set `IHarnessMessage.order` to `{ messageIndex, partIndex }`; explicitly ordered messages retain that relative order while timestamps continue to place legacy messages and ancillary cards. Consecutive tool calls use one generic responsive-grid pipeline at their chronological position, partitioned by descriptor layout: compact tools share rows with compact tools, subagents share rows only with subagents, and terminal plus file-write cards span a whole row. Consecutive same-layout runs are always capped at four cards per group row, even when their tool kinds differ. Columns respond to the row's own width rather than the viewport, and status changes never regroup cards. Because a compact group sits inside the content measure, its column count is bounded by that measure — two columns at the 820px default; the wider three- and four-column steps apply when a host widens `--dees-harness-content-width`. Rows are held to a reading measure — `--dees-harness-content-width`, default `820px`, capped at the available width — while terminal and file-write rows keep the full pane, so prose stays readable and diffs and command output get the room they need. A subagent group takes the pane only from three cards upward; one or two stay on the measure. A terminal or file-write card keeps the pane only while it is open: once collapsed it returns to the reading measure, because a one-line header does not need the room its output did. This holds for every card in a terminal/file-write group, not only a lone one — those cards span all the columns of their row and stack vertically, so each follows its own disclosure and a collapsed command can sit above an open diff. The row itself still spans the pane; the card centres inside it and animates between the two widths with the standard disclosure timing. The genuinely side-by-side grids — compact tools and subagents — are never offered this width and keep their cell width in both states. The list publishes the collapsed width to its terminal/file-write rows as `--dees-harness-collapsed-tool-width`, derived from `--dees-harness-content-width`; a host can set it on a row to widen those cards, or set `100%` to keep collapsed cards full-bleed; setting it on the chat or the list also reaches compact and subagent cells, so prefer row-level overrides. The docked composer keeps its full-bleed bar and insets its content to the same measure, so the prompt lines up with the messages above it. The status line under the transcript sits on that measure too, so the working indicator starts where the messages start rather than at the pane edge. Set the custom property on the chat or the list to widen or disable the column; it is read when rows are measured, so change it at mount rather than while a virtualized transcript is scrolled. Untouched projected subtask cards collapse individually on completion with the normal disclosure animation; other cards retain their descriptor or user-selected disclosure state. Auto-follow responds to streamed growth but does not repeatedly repin while a disclosure shrinks. Permission requests, questions, and nested-stream limits behave as before. Props: `messages`, `permissions`/`permissionsBusy`, `questions`, `status`, `viewState`, `transcriptKey`, `hasEarlierMessages`, `loadingEarlier`, `autoFollow`, `hideScrollbar`, `fadeScrollEdges`, `showRoles`, `showTimestamps`, `allowSubtaskStreams`, `markdownWhileStreaming` (default `true`), `emptyTitle`/`emptyText` (+ `slot="empty"`). Native browser find and selection cover the mounted window; hosts that require complete-history search or copy should operate on their canonical `messages`, `permissions`, and `questions` models.
2644
+ Producers with authoritative source chronology can set `IHarnessMessage.order` to `{ messageIndex, partIndex }`; explicitly ordered messages retain that relative order while timestamps continue to place legacy messages and ancillary cards. Consecutive tool calls use one generic responsive-grid pipeline at their chronological position, partitioned by descriptor layout: compact tools share rows with compact tools, subagents share rows only with subagents, and terminal plus file-write cards span a whole row. Consecutive same-layout runs are always capped at four cards per group row, even when their tool kinds differ. Columns respond to the row's own width rather than the viewport, and status changes never regroup cards. Because a compact group sits inside the content measure, its column count is bounded by that measure — two columns at the 820px default; the wider three- and four-column steps apply when a host widens `--dees-harness-content-width`. Rows are held to a reading measure — `--dees-harness-content-width`, default `820px`, capped at the available width — while terminal and file-write rows keep the full pane, so prose stays readable and diffs and command output get the room they need. A subagent group takes the pane only from three cards upward; one or two stay on the measure. A terminal or file-write card keeps the pane only while it is open: once collapsed it returns to the reading measure, because a one-line header does not need the room its output did. This holds for every card in a terminal/file-write group, not only a lone one — those cards span all the columns of their row and stack vertically, so each follows its own disclosure and a collapsed command can sit above an open diff. The row itself still spans the pane; the card surface centres inside it and interpolates between the two widths on the standard disclosure timing, in step with the body height and the outcome line, so a toggle never snaps at either end. A card that mounts collapsed is already at its measure and does not animate into it. The genuinely side-by-side grids — compact tools and subagents — are never offered this width and keep their cell width in both states. The list publishes the collapsed width to its terminal/file-write rows as `--dees-harness-collapsed-tool-width`, derived from `--dees-harness-content-width`; a host can set it on a row to widen those cards, or set `100%` to keep collapsed cards full-bleed; setting it on the chat or the list also reaches compact and subagent cells, so prefer row-level overrides. The docked composer keeps its full-bleed bar and insets its content to the same measure, so the prompt lines up with the messages above it. The status line under the transcript sits on that measure too, so the working indicator starts where the messages start rather than at the pane edge. Set the custom property on the chat or the list to widen or disable the column; it is read when rows are measured, so change it at mount rather than while a virtualized transcript is scrolled. Untouched projected subtask cards collapse individually on completion with the normal disclosure animation; other cards retain their descriptor or user-selected disclosure state. Auto-follow responds to streamed growth but does not repeatedly repin while a disclosure shrinks. Permission requests, questions, and nested-stream limits behave as before. Props: `messages`, `permissions`/`permissionsBusy`, `questions`, `status`, `viewState`, `transcriptKey`, `hasEarlierMessages`, `loadingEarlier`, `autoFollow`, `hideScrollbar`, `fadeScrollEdges`, `showRoles`, `showTimestamps`, `allowSubtaskStreams`, `markdownWhileStreaming` (default `true`), `emptyTitle`/`emptyText` (+ `slot="empty"`). Native browser find and selection cover the mounted window; hosts that require complete-history search or copy should operate on their canonical `messages`, `permissions`, and `questions` models.
2644
2645
 
2645
2646
  #### `DeesHarnessMessage`
2646
2647
 
@@ -2654,7 +2655,7 @@ One message: role accent, markdown (assistant default, `message.markdown` overri
2654
2655
 
2655
2656
  #### `DeesHarnessToolCard`
2656
2657
 
2657
- Tool-call card: shared chrome (icon, label, subtitle, status pill, duration, MCP badges) with a per-kind body. Kind resolution goes through `DeesHarnessToolRegistry` (see below); bodies for the seeded kinds — terminal, file-read, file-write (renders a before/after diff via `dees-dataview-codebox` when the input carries previous content), file-delete, dir-list, browser (inline screenshots), http, json, search, subtask, project-task, todo, mcp, unknown — live in `renderers.ts`. File-write cards retain the user's inline/split choice while the same call updates. Numeric patch hunks preserve their source line numbers; patches and replacement snippets without coordinates hide gutters rather than presenting fabricated positions. A producer-supplied `IHarnessToolCall.title` leads the subtitle when present — a harness's own one-line description of the call, such as `Read SKILL.md` for a shell invocation — and the descriptor-derived line stays reachable as the hover tooltip; a blank title falls back to the derived line. Overflowing subtitles use a 40px right-edge fade and reveal the full line on hover plus once after a two-second initial delay. Expand/collapse always uses a smooth disclosure transition. Initially closed bodies mount lazily on first expansion, then retain the same DOM and become inert plus `aria-hidden` while closed. A closed card also reflects `data-collapsed` on its host and honours `--dees-harness-collapsed-tool-width` as its own `max-width`, so a transcript row can hold a collapsed card to a narrower measure than an open one; unset — a direct tool-card host, a grid cell — the card fills its row in both states. Long outputs truncate at 6,000 chars with a "Show all" expander. Terminal and error cards open by default, and a terminal card collapses itself again once its command has finished without reporting a failure. A producer whose failed commands come back as `completed` with the failure only in the output must signal it through `exitCode`, `isError` or `status: 'error'`, or the card collapses behind its one-line outcome. A missing exit code is not a failure — most harnesses never send one — so a completed command collapses whether or not it reported `exitCode: 0`. A non-zero exit, an error status, `isError`, and a stopped command all keep their output on screen. A collapsed terminal shows a one-line outcome (exit code when the call carried one, line count or `no output`, and the last meaningful line) beneath the command; an exit code is never invented for a call that did not report one. It is derived from the call's own output rather than from any producer-supplied description, so it is present for every producer. An explicit expand or collapse by the user always wins and is never overridden when the call settles. Descriptors opt in through `collapseWhenFinished` and supply the line through `summary`. Terminal output uses a black inner log surface and follows the live tail after reaching the card-height cap. Scrolling upward detaches the tail; leaving the card returns to the bottom after five seconds, while re-entering cancels that pending return. Terminal calls may provide `exitCode` separately when `output` is one combined process stream; the renderer retains support for `{ stdout, stderr, exitCode }` output objects. A subagent card is capped by `--dees-harness-active-subtask-height` (default `320px`) rather than fixed to it: a settled agent whose brief and result fit sizes to its own text, and only content past the cap hands the overflow to the panels, which scroll with a hidden scrollbar and a fade mask at the clipped edge. Both the Agent Brief and the Result carry that affordance, and the drill-in action stays pinned at the bottom of the card. A live child keeps the full window so its streaming transcript has a stable area to scroll in.
2658
+ Tool-call card: shared chrome (icon, label, subtitle, status pill, duration, MCP badges) with a per-kind body. Kind resolution goes through `DeesHarnessToolRegistry` (see below); bodies for the seeded kinds — terminal, file-read, file-write (renders a before/after diff via `dees-dataview-codebox` when the input carries previous content), file-delete, dir-list, browser (inline screenshots), http, json, search, subtask, project-task, todo, mcp, unknown — live in `renderers.ts`. File-write cards retain the user's inline/split choice while the same call updates. Numeric patch hunks preserve their source line numbers; patches and replacement snippets without coordinates hide gutters rather than presenting fabricated positions. A producer-supplied `IHarnessToolCall.title` leads the subtitle when present — a harness's own one-line description of the call, such as `Read SKILL.md` for a shell invocation — and the descriptor-derived line stays reachable as the hover tooltip; a blank title falls back to the derived line. Overflowing subtitles use a 40px right-edge fade and reveal the full line on hover plus once after a two-second initial delay. Expand/collapse always uses a smooth disclosure transition: the card surface width, the body height and the collapsed outcome line all run on `--dees-transition-default` with `--dees-ease-standard`, started by the same update, and all three stop together under `prefers-reduced-motion: reduce`. Width is constrained on the card surface rather than the host, because the shared default stylesheet applies a universal background/colour transition to every element and that rule reaches this element from its parent's tree, where it outranks any host-scoped rule. Initially closed bodies mount lazily on first expansion, then retain the same DOM and become inert plus `aria-hidden` while closed; the outcome line likewise stays mounted and is hidden from assistive technology while the card is open. A closed card also reflects `data-collapsed` on its host and honours `--dees-harness-collapsed-tool-width` as its own `max-width`, so a transcript row can hold a collapsed card to a narrower measure than an open one; unset — a direct tool-card host, a grid cell — the card fills its row in both states. Long outputs truncate at 6,000 chars with a "Show all" expander. Terminal and error cards open by default, and a terminal card collapses itself again once its command has finished without reporting a failure. A producer whose failed commands come back as `completed` with the failure only in the output must signal it through `exitCode`, `isError` or `status: 'error'`, or the card collapses behind its one-line outcome. A missing exit code is not a failure — most harnesses never send one — so a completed command collapses whether or not it reported `exitCode: 0`. A non-zero exit, an error status, `isError`, and a stopped command all keep their output on screen. A collapsed terminal shows a one-line outcome (exit code when the call carried one, line count or `no output`, and the last meaningful line) beneath the command, which opens and closes with the same timing as the body so the header never steps by a line; an exit code is never invented for a call that did not report one. It is derived from the call's own output rather than from any producer-supplied description, so it is present for every producer. An explicit expand or collapse by the user always wins and is never overridden when the call settles. Descriptors opt in through `collapseWhenFinished` and supply the line through `summary`. Terminal output uses a black inner log surface and follows the live tail after reaching the card-height cap. Scrolling upward detaches the tail; leaving the card returns to the bottom after five seconds, while re-entering cancels that pending return. Terminal calls may provide `exitCode` separately when `output` is one combined process stream; the renderer retains support for `{ stdout, stderr, exitCode }` output objects. A subagent card is capped by `--dees-harness-active-subtask-height` (default `320px`) rather than fixed to it: a settled agent whose brief and result fit sizes to its own text, and only content past the cap hands the overflow to the panels, which scroll with a hidden scrollbar and a fade mask at the clipped edge. Both the Agent Brief and the Result carry that affordance, and the drill-in action stays pinned at the bottom of the card. A live child keeps the full window so its streaming transcript has a stable area to scroll in.
2658
2659
 
2659
2660
  After mutating a canonical tool call in place, call `refreshCall()`. It refreshes the mounted card, any mounted nested subtask transcript, and any open fullscreen card while retaining the same canonical call and nested-message object references; it does not clone or replace them.
2660
2661
 
@@ -2767,6 +2768,25 @@ Scratchpad edits emit `harness-scratchpad-save` with `{ text, expectedRevision }
2767
2768
 
2768
2769
  Direct sidebar hosts can set `intelligenceAvailabilityStatus`, `intelligenceUnavailableReason`, and `intelligenceHeading`; `intelligenceEnabled` still controls whether the section exists. The properties default to `available`, an empty reason, and `Session Intelligence`. A blank heading falls back to `Session Intelligence`, and unavailable state without a reason renders `<heading> is unavailable.` Only `available` permits `harness-session-intelligence-ask`; checking and unavailable states retain history and expose accessible status text. The availability union is exported as `THarnessIntelligenceAvailabilityStatus`.
2769
2770
 
2771
+ #### `DeesHarnessConversationPicker`
2772
+
2773
+ Keyboard-first picker for one conversation, or one attachable resource, used wherever a menu would otherwise list every possible target. It is presentation only: it never mutates a session, a resource, or an association — it resolves one choice and leaves the change to the host.
2774
+
2775
+ Set `.sessions`, `.groups` and `.resources` to the same models the list renders. `.excludeTargets` removes targets that would be a no-op (a resource's existing attachments), and `.eligibleTargets` restricts the offer to a host-declared allow-list; absent means unrestricted. Typing filters on title, project label and group name — the same fields the session list searches. Up/Down move the active row, Enter picks it, Escape cancels. `harness-picker-choose` carries `{ target: IHarnessSessionListItemRef }`; `harness-picker-cancel` carries nothing.
2776
+
2777
+ `DeesHarnessConversationPicker.pick(options)` is the one-call form: it opens the picker inside `DeesModal`, resolves with the chosen ref or `null` on cancel, closes the modal, and returns focus to whatever had it before. Reuse it for disambiguation as well — pass a resource's current attachments as `eligibleTargets` to ask which attachment a Move or Detach applies to.
2778
+
2779
+ ```typescript
2780
+ const target = await DeesHarnessConversationPicker.pick({
2781
+ sessions: list.sessions,
2782
+ groups: list.groups,
2783
+ excludeTargets: currentTargets,
2784
+ eligibleTargets: list.eligibleTargetsByResourceId[resource.id],
2785
+ heading: `Attach ${resource.title} to…`,
2786
+ });
2787
+ if (target) detail.requestAssociation(target, currentTargets.length ? 'attach-additional' : 'attach');
2788
+ ```
2789
+
2770
2790
  #### `DeesHarnessSessionList`
2771
2791
 
2772
2792
  Searchable, card-based conversation column for sidebars or a `DeesModal` (`harness-session-select` on click, `harness-session-open` on double-click/Enter). Cards stay collapsed until their independent accessible expansion control is used. Selection and working-state changes do not affect expansion.
@@ -2785,22 +2805,29 @@ The compact **New group** button sits beside search whenever `enableGrouping` is
2785
2805
 
2786
2806
  The Sessions demo pairs the list with a selected-session workspace. Its controls step through starting, working, feedback, approval, completion and failure without timers or backend operations. Question and permission responses update the selected session; declining approval pauses it. The narrow layout switches between the preserved list and detail panes with an **All sessions** control that restores row focus.
2787
2807
 
2788
- Generic host resources use the exported `IHarnessResourceMeta` and `IHarnessResourceAssociation` contracts. Set `.resources`, `.resourceAssociations`, `.selectedResourceId`, and `.eligibleTargetsByResourceId`. Resource rows render as top-level rows in the ordered list, never as children of a session. Optional `projectLabel` renders on the resource metadata line after the kind and is searched like the title and preview. Resource metadata is presentation-only: the list never mounts a terminal, browser, file viewer, or other runtime.
2808
+ Generic host resources use the exported `IHarnessResourceMeta` and `IHarnessResourceAssociation` contracts. Set `.resources`, `.resourceAssociations`, `.selectedResourceId`, and `.eligibleTargetsByResourceId`. Resource rows render as top-level rows in the ordered list, never as children of a session. Optional `projectLabel` renders on the resource metadata line after the kind and is searched like the title and preview. A resource can be attached to several items at once: `IHarnessResourceAssociation.targets` is a set of refs, de-duplicated on projection, and the row shows one marker per attachment — two by name, the rest as a `+N` count with every name in the row's title. `IHarnessResourceEventDetail.targets` carries the same set on select, open and context events. Resource metadata is presentation-only: the list never mounts a terminal, browser, file viewer, or other runtime.
2789
2809
 
2790
- **Attaching is a separate explicit action, not a drop.** An association is an access grant: it shows as a decorative marker on the resource row (a link icon plus the target's title, resolved from `sessions` or `resources` regardless of search or collapse) and never moves the row. Dropping is purely positional and never attaches. The association stays single-valued — a resource has at most one target — and the target is any other item, so a resource may be attached to a session **or** to another resource such as a terminal:
2810
+ **Attaching is a deliberate gesture, never an accident of reordering.** An association is an access grant: it shows as a decorative marker on the resource row (a link icon plus each target's title, resolved from `sessions` or `resources` regardless of search or collapse) and never moves the row. A resource dropped on the **centre half of a row** attaches to it; the outer quarters of that same row, and every gap between rows, stay purely positional. The row being targeted shows a ring, a tint and a link badge, and no insertion gap opens while it does. A drag attaches an unattached resource and moves a singly attached one. A resource attached to several conversations is only repositioned by drag — it offers no attach affordance on any row, because a drag cannot say which of its attachments was meant; use **Move to…** for that. Keyboard moves stay positional; attaching by keyboard goes through the host's menu. The target is any other item, so a resource may be attached to a session **or** to another resource such as a terminal, and to several at once:
2791
2811
 
2792
2812
  ```typescript
2793
2813
  list.resourceAssociations = [
2794
- { resourceId: 'file:notes', target: { kind: 'resource', id: 'terminal:build' } },
2814
+ { resourceId: 'file:notes', targets: [{ kind: 'resource', id: 'terminal:build' }] },
2815
+ {
2816
+ resourceId: 'terminal:build',
2817
+ targets: [{ kind: 'session', id: 's-4' }, { kind: 'session', id: 's-0' }],
2818
+ },
2795
2819
  ];
2796
2820
  ```
2797
2821
 
2798
- Association records are projected only when the resource exists, the target exists, the target is not the resource itself, and no earlier record claimed that resource. `eligibleTargetsByResourceId` restricts attach/reassign targets for host menus: a missing entry leaves the resource unrestricted, an empty array means no eligible target. Which resource kinds may host an attachment is the host's rule, not the component's.
2822
+ Targets are projected only when the resource exists, the target exists, the target is not the resource itself, and no earlier target in the same record already claimed it. `eligibleTargetsByResourceId` restricts targets for both the host menu and the drop affordance: a missing entry leaves the resource unrestricted, an empty array means no eligible target, and an ineligible row offers no attach affordance and commits nothing. Which resource kinds may host an attachment is the host's rule, not the component's.
2823
+
2824
+ `requestResourceAssociation(resourceId, toTarget, source?, mode?, fromTarget?)` and the context detail's `requestAssociation(toTarget, mode?, fromTarget?)` emit `harness-resource-associate` with `THarnessResourceAssociateDetail`: `mode` is `attach`, `attach-additional`, `move` or `detach`, and `fromTarget` names the single attachment being replaced or removed. With no mode, an unattached resource attaches and a singly attached one moves; a resource with several attachments is refused, because only the host knows which one the user meant — pass a mode and a `fromTarget`, or ask with `DeesHarnessConversationPicker`.
2799
2825
 
2800
2826
  ```typescript
2801
- import type {
2802
- IHarnessResourceContextDetail,
2803
- THarnessResourceAssociationRequestDetail,
2827
+ import {
2828
+ DeesHarnessConversationPicker,
2829
+ type IHarnessResourceContextDetail,
2830
+ type THarnessResourceAssociateDetail,
2804
2831
  } from '@design.estate/dees-catalog';
2805
2832
 
2806
2833
  const list = document.querySelector('dees-harness-session-list')!;
@@ -2810,37 +2837,42 @@ list.resources = [
2810
2837
  { id: 'file:notes', kind: 'file', title: 'Release notes', preview: '6.7.0 draft' },
2811
2838
  ];
2812
2839
  list.resourceAssociations = [
2813
- { resourceId: 'shell:build', target: { kind: 'session', id: 'session-1' } },
2840
+ { resourceId: 'shell:build', targets: [{ kind: 'session', id: 'session-1' }] },
2814
2841
  ];
2815
2842
  list.eligibleTargetsByResourceId = {
2816
2843
  'shell:build': [{ kind: 'session', id: 'session-1' }, { kind: 'session', id: 'session-2' }],
2817
2844
  'file:notes': [{ kind: 'session', id: 'session-2' }, { kind: 'resource', id: 'shell:build' }],
2818
2845
  };
2819
2846
 
2820
- list.addEventListener('harness-resource-association-request', async (event) => {
2821
- const resourceEvent = event as CustomEvent<THarnessResourceAssociationRequestDetail>;
2822
- const { resource, toTarget } = resourceEvent.detail;
2823
- await persistAssociation(resource.id, toTarget);
2847
+ list.addEventListener('harness-resource-associate', async (event) => {
2848
+ const resourceEvent = event as CustomEvent<THarnessResourceAssociateDetail>;
2849
+ const { resource, mode, fromTarget, toTarget } = resourceEvent.detail;
2850
+ await persistAssociation(resource.id, { mode, fromTarget, toTarget });
2824
2851
  list.resourceAssociations = await loadAssociations();
2825
2852
  });
2826
2853
 
2827
- list.addEventListener('harness-resource-context', (event) => {
2854
+ list.addEventListener('harness-resource-context', async (event) => {
2828
2855
  const resourceEvent = event as CustomEvent<IHarnessResourceContextDetail>;
2829
- openHostMenu({
2830
- x: resourceEvent.detail.clientX,
2831
- y: resourceEvent.detail.clientY,
2832
- attach: (target) => resourceEvent.detail.requestAssociation(target),
2833
- detach: () => resourceEvent.detail.requestAssociation(null),
2834
- onClose: resourceEvent.detail.close,
2856
+ const { resource, targets } = resourceEvent.detail;
2857
+ resourceEvent.detail.close();
2858
+ const target = await DeesHarnessConversationPicker.pick({
2859
+ sessions: list.sessions,
2860
+ groups: list.groups,
2861
+ excludeTargets: targets,
2862
+ eligibleTargets: list.eligibleTargetsByResourceId[resource.id],
2863
+ heading: `Attach ${resource.title} to…`,
2835
2864
  });
2865
+ if (target) {
2866
+ resourceEvent.detail.requestAssociation(target, targets.length ? 'attach-additional' : 'attach');
2867
+ }
2836
2868
  });
2837
2869
  ```
2838
2870
 
2839
- `harness-resource-select` and `harness-resource-open` carry `{ resource, target }`, where `target` is the controlled association ref or null. `harness-resource-context` adds viewport coordinates, the pointer/Actions/keyboard source, the original event, `requestAssociation(toTarget)`, and `close()` for focus restoration. The visible Actions button has menu disclosure semantics; native `ContextMenu` and `Shift+F10` invoke the same host-owned path. The component does not require `DeesContextmenu` and does not own menu choices.
2871
+ `harness-resource-select` and `harness-resource-open` carry `{ resource, targets }`, where `targets` is the controlled set of association refs and is empty for an unattached resource. `harness-resource-context` adds viewport coordinates, the pointer/Actions/keyboard source, the original event, `requestAssociation(toTarget, mode?, fromTarget?)`, and `close()` for focus restoration. The visible Actions button has menu disclosure semantics; native `ContextMenu` and `Shift+F10` invoke the same host-owned path. The component does not require `DeesContextmenu` and does not own menu choices.
2840
2872
 
2841
- All component-originated association request paths converge on the public `requestResourceAssociation(resourceId, toTarget, source?)` method. A valid change emits one bubbling/composed `harness-resource-association-request` discriminated by `intent: 'attach' | 'detach' | 'reassign'` and carrying `resource`, `fromTarget`, `toTarget`, and `source: 'action' | 'keyboard'`. The method returns `false` and emits nothing for unknown resources, unknown or ineligible targets, a target that is the resource itself, detach from an unattached resource, or the current target. It never mutates resources, associations, selection, groups, or order.
2873
+ All component-originated association paths converge on the public `requestResourceAssociation(resourceId, toTarget, source?, mode?, fromTarget?)` method. A valid change emits one bubbling/composed `harness-resource-associate` discriminated by `mode: 'attach' | 'attach-additional' | 'move' | 'detach'` and carrying `resource`, `fromTarget`, `toTarget`, and `source: 'action' | 'keyboard' | 'pointer'`. The method returns `false` and emits nothing for unknown resources, unknown or ineligible targets, a target that is the resource itself, a target already in the set, a `move`/`detach` whose `fromTarget` is not in the set, an `attach` on an already attached resource, an `attach-additional` on an unattached one, or an ambiguous request with no mode. It never mutates resources, associations, selection, groups, or order.
2842
2874
 
2843
- Association projection is deterministic and non-destructive: resource records without a non-empty ID, kind, or title, unknown resource/target references, self-targets, and later duplicate records for a resource are ignored; the first valid association wins. Duplicate resource metadata IDs likewise use the first resource. Eligibility does not hide an existing authoritative association, which remains visible and detachable. Requested changes are announced separately from completion; completion is announced only after the controlled association input reflects the requested target.
2875
+ Association projection is deterministic and non-destructive: resource records without a non-empty ID, kind, or title, unknown resource/target references, self-targets, and repeated targets are ignored; several records for one resource merge into one de-duplicated set. Duplicate resource metadata IDs likewise use the first resource. Eligibility does not hide an existing authoritative association, which remains visible and detachable. Requested changes are announced separately from completion; completion is announced only after the controlled association input reflects the requested set.
2844
2876
 
2845
2877
  Search covers resource ID, kind, title, and preview, and matches sessions and resources as peers — a matching session no longer reveals its attached resources, and a matching resource no longer reveals its target. Resources render with zero sessions. Resource dragging edge-scrolls the list exactly like session dragging and emits only `harness-item-move`. Use the Actions/context path for keyboard and mobile association workflows; coarse-pointer grip and Actions targets are at least 44px.
2846
2878
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '11.2.1',
6
+ version: '12.0.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
  }
@@ -0,0 +1,46 @@
1
+ import { html } from '@design.estate/dees-element';
2
+ import { demoPage } from '../../../demos/templates.js';
3
+ import type { IHarnessSessionGroup, IHarnessSessionMeta, IHarnessResourceMeta } from '../interfaces.js';
4
+
5
+ const demoSessions: IHarnessSessionMeta[] = [
6
+ { id: 's1', title: 'Fix sitemap cache key', projectLabel: 'storefront', updatedAt: Date.now() - 1_000 },
7
+ { id: 's2', title: 'Investigate model rate limits', projectLabel: 'gateway', updatedAt: Date.now() - 2_000 },
8
+ { id: 's3', title: 'Prepare the catalog release', projectLabel: 'catalog', updatedAt: Date.now() - 3_000 },
9
+ { id: 's4', title: 'Improve agent onboarding', updatedAt: Date.now() - 4_000 },
10
+ ];
11
+
12
+ const demoResources: IHarnessResourceMeta[] = [
13
+ { id: 'terminal:build', kind: 'terminal', title: 'Build shell', icon: 'lucide:Terminal', projectLabel: 'storefront' },
14
+ ];
15
+
16
+ const demoGroups: IHarnessSessionGroup[] = [
17
+ { id: 'active', name: 'Workspace', itemIds: [{ kind: 'session', id: 's1' }, { kind: 'session', id: 's2' }] },
18
+ { id: 'review', name: 'Review', itemIds: [{ kind: 'session', id: 's3' }] },
19
+ ];
20
+
21
+ export const demoFunc = () => demoPage(
22
+ 'Conversation picker',
23
+ 'Keyboard-first target picker: type to filter on title, project label or group, Enter picks, Escape cancels.',
24
+ html`
25
+ <style>
26
+ .pickerDemo {
27
+ max-width: 420px;
28
+ padding: 16px;
29
+ border: 1px solid var(--dees-color-border-subtle);
30
+ border-radius: var(--dees-radius-xl);
31
+ background: var(--dees-color-bg-secondary);
32
+ }
33
+ </style>
34
+ <div class="pickerDemo">
35
+ <dees-harness-conversation-picker
36
+ heading="Attach Build shell to…"
37
+ .sessions=${demoSessions}
38
+ .groups=${demoGroups}
39
+ .resources=${demoResources}
40
+ .excludeTargets=${[{ kind: 'session' as const, id: 's4' }]}
41
+ @harness-picker-choose=${(event: CustomEvent) => console.log('picked', event.detail.target)}
42
+ @harness-picker-cancel=${() => console.log('cancelled')}
43
+ ></dees-harness-conversation-picker>
44
+ </div>
45
+ `,
46
+ );