@adea-ai/ui 0.67.1 → 0.68.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 (30) hide show
  1. package/README.md +58 -0
  2. package/dist/NOTICE +34 -0
  3. package/dist/components/conversation/index.d.ts +1 -0
  4. package/dist/components/conversation/index.d.ts.map +1 -1
  5. package/dist/components/conversation/index.js +2 -1
  6. package/dist/components/conversation/transcript-composition.d.ts +66 -0
  7. package/dist/components/conversation/transcript-composition.d.ts.map +1 -0
  8. package/dist/components/conversation/transcript-composition.js +395 -0
  9. package/dist/components/conversation/transcript-composition.js.map +1 -0
  10. package/dist/components/conversation/transcript-folding.d.ts +22 -0
  11. package/dist/components/conversation/transcript-folding.d.ts.map +1 -0
  12. package/dist/components/conversation/transcript-folding.js +43 -0
  13. package/dist/components/conversation/transcript-folding.js.map +1 -0
  14. package/dist/components/conversation/transcript-grouping.d.ts +54 -0
  15. package/dist/components/conversation/transcript-grouping.d.ts.map +1 -0
  16. package/dist/components/conversation/transcript-grouping.js +155 -0
  17. package/dist/components/conversation/transcript-grouping.js.map +1 -0
  18. package/dist/index.js +2 -1
  19. package/dist/r/conversation.json +15 -0
  20. package/dist/r/registry.json +15 -0
  21. package/dist/r/src/components/conversation/index.ts +17 -0
  22. package/dist/r/src/components/conversation/transcript-composition.tsx +555 -0
  23. package/dist/r/src/components/conversation/transcript-folding.ts +61 -0
  24. package/dist/r/src/components/conversation/transcript-grouping.ts +204 -0
  25. package/package.json +8 -1
  26. package/registry.json +15 -0
  27. package/src/components/conversation/index.ts +17 -0
  28. package/src/components/conversation/transcript-composition.tsx +555 -0
  29. package/src/components/conversation/transcript-folding.ts +61 -0
  30. package/src/components/conversation/transcript-grouping.ts +204 -0
package/README.md CHANGED
@@ -448,3 +448,61 @@ draft state, persistence, message reconciliation and editor behavior. The model
448
448
  does not read storage or the clipboard, upload files, edit a textarea, or
449
449
  provide undo/history, previews or chips. Its selected source and Apache-2.0
450
450
  attribution are recorded in the issue #532 entry in `NOTICE`.
451
+
452
+ ## Transcript composition
453
+
454
+ `TranscriptComposition` assembles ordered host-classified rows, contiguous groups,
455
+ turn presentation and accessible fold controls. Import it from the public component
456
+ subpath:
457
+
458
+ ```tsx
459
+ import {
460
+ TranscriptComposition,
461
+ type TranscriptCompositionRendererProps,
462
+ type TranscriptRow,
463
+ } from '@adea-ai/ui/components/conversation/transcript-composition'
464
+
465
+ type Message = { text: string }
466
+ const rows: readonly TranscriptRow<Message>[] = [
467
+ { id: 'question-1', value: { text: 'Inspect the cursor.' }, opensTurn: 'reset' },
468
+ {
469
+ id: 'tool-1',
470
+ value: { text: 'Read the cursor.' },
471
+ fold: 'tool',
472
+ call: { phase: 'request', id: 'call-1' },
473
+ },
474
+ ]
475
+
476
+ function HostMessage(props: TranscriptCompositionRendererProps<Message>) {
477
+ return <article>{props.row.value.text}</article>
478
+ }
479
+
480
+ export function TranscriptPreview() {
481
+ return <TranscriptComposition rows={rows} renderRow={HostMessage} />
482
+ }
483
+ ```
484
+
485
+ Each row needs a stable unique `id` within its transcript. The host supplies the
486
+ Solid row component and classifies turn boundaries, calls, conclusions,
487
+ always-visible actions and redacted values. The renderer does not parse protocol
488
+ payloads or decide permission, session, event, delivery or success state.
489
+
490
+ Settled tool folds start closed and unmount their hidden row payloads. Live rows
491
+ stay open until the host marks them settled; an explicit row choice survives that
492
+ running-state transition. `foldMode="prose"` folds host-marked eligible activity
493
+ before a host-designated conclusion, leaving that conclusion visible.
494
+ The `foldState="collapsed"` command keeps hidden
495
+ content mounted. It folds eligible rows before a host-marked conclusion; without
496
+ that boundary it leaves prose visible and can still collapse explicitly classified
497
+ tool calls. Always-visible rows stay in place. `disclosure` plus
498
+ `onDisclosureChange` lets an application restore explicit choices from its own
499
+ durable store using `transcriptDisclosureKey`.
500
+
501
+ `bun run check:packed-transcript` installs the actual tarball without optional
502
+ chart/carousel engines, compiles both published export conditions, and checks the
503
+ same behavior in headless Chromium, WebKit and native Node SSR. It covers row-owner
504
+ identity through loose-to-turn promotion, tool request counting, mounted prose,
505
+ keyboard disclosure, visible actions and automated accessibility. This is shared
506
+ composition evidence; bounded runtime projection, storage and production chat
507
+ integration remain application acceptance gates. Attribution for the selected
508
+ KiroCrew source contracts is recorded in the issue-specific `NOTICE` entry.
package/dist/NOTICE CHANGED
@@ -291,3 +291,37 @@ refused. React, Lexical, browser clipboard/File handling, localStorage side-tabl
291
291
  recovery, backend message reconciliation, uploads, textarea editing, previews,
292
292
  chips, undo/history and submit/persistence authority are not included. This
293
293
  selected model is not rich editing, complete Chat, or application adoption.
294
+
295
+ ## Shared transcript composition translated from KiroCrew
296
+
297
+ `packages/ui/src/components/conversation/transcript-grouping.ts`,
298
+ `transcript-folding.ts` and the `transcript-composition.tsx` component substantially
299
+ translate the ordered display grouping and turn disclosure contracts in
300
+ `website/src/app-sdk/ChatMessageList.tsx`,
301
+ `website/src/pages/chat/groupDisplayItems.ts`,
302
+ `website/src/pages/chat/TurnBlock.tsx`,
303
+ `website/src/pages/chat/CollapsibleToolGroup.tsx` and
304
+ `website/src/pages/chat/rowDisclosure.tsx`, pinned at KiroCrew revision
305
+ `283e136c0f902e965a535a7c9548c57c7504fed0`.
306
+
307
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
308
+ This product includes software developed at Amazon.com, Inc.
309
+ (https://www.amazon.com/). Licensed under the Apache License, Version 2.0.
310
+
311
+ Retained: ordered host-classified turns and contiguous groups, structural sharing,
312
+ stable row owners during loose-to-turn promotion, running/default disclosure,
313
+ host-restorable explicit pins, distinct request counting, tool-payload unmounting,
314
+ mounted interim prose, and visible host-marked actionable or interactive rows.
315
+ The host supplies row identity, redaction, conclusion and call classifications;
316
+ the renderer owns no protocol parser, permission, session, event or success
317
+ authority. Solid/Kobalte-compatible controls and keyed row owners replace React,
318
+ Redux and Framer Motion ownership. Relevant test contracts come from
319
+ `website/src/test/groupDisplayItems.test.ts`,
320
+ `website/src/test/groupDisplayItems.structuralSharing.test.ts`,
321
+ `website/src/test/TurnBlock.test.tsx`,
322
+ `website/src/test/TurnBlock.disclosurePin.test.tsx`,
323
+ `website/src/test/TurnBlock.disclosureDurable.test.tsx`,
324
+ `website/src/test/TurnBlock.rowMemo.test.tsx` and
325
+ `website/src/test/rowDisclosure.test.tsx`; compiled and Solid packed consumers are
326
+ exercised in headless Chromium, WebKit and native Node SSR. Full runtime projection,
327
+ durable application storage and production Chat adoption remain host-owned gates.
@@ -4,6 +4,7 @@ export { ConversationAvatar, type ConversationAvatarKind, type ConversationAvata
4
4
  export { ConversationSurface, type ConversationSurfaceProps, type ConversationReadingPosition, } from './conversation-surface';
5
5
  export { ComposerAttachmentButton, ComposerHint, MessageComposer, type MessageComposerProps, } from './message-composer';
6
6
  export { MessageBody, MessageDayDivider, MessageGroup, MessageRow, type MessageRowProps, } from './message-row';
7
+ export { TranscriptComposition, transcriptDisclosureKey, type TranscriptCompositionProps, type TranscriptCompositionFold, type TranscriptCompositionRowContext, type TranscriptCompositionRendererProps, type TranscriptDisplayKind, type GroupedTranscript, type TranscriptDisplayItem, type TranscriptFoldState, type TranscriptFoldMode, type TranscriptFoldPlan, type TranscriptMounting, type TranscriptRow, type TranscriptTurnItem, } from './transcript-composition';
7
8
  export { AttachmentCard, ThreadPanel, type AttachmentCardProps, type ThreadPanelProps, } from './thread-panel';
8
9
  export { countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, type PasteBlock, } from './paste-tokens';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAChG,OAAO,EACL,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,GACjC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,cAAc,EACd,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,YAAY,EACZ,OAAO,EACP,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAChG,OAAO,EACL,kBAAkB,EAClB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,GACjC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,+BAA+B,EACpC,KAAK,kCAAkC,EACvC,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,cAAc,EACd,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,YAAY,EACZ,OAAO,EACP,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAA"}
@@ -4,6 +4,7 @@ import { ConversationAvatar } from "./conversation-avatar.js";
4
4
  import { ConversationSurface } from "./conversation-surface.js";
5
5
  import { ComposerAttachmentButton, ComposerHint, MessageComposer } from "./message-composer.js";
6
6
  import { MessageBody, MessageDayDivider, MessageGroup, MessageRow } from "./message-row.js";
7
+ import { TranscriptComposition, transcriptDisclosureKey } from "./transcript-composition.js";
7
8
  import { AttachmentCard, ThreadPanel } from "./thread-panel.js";
8
9
  import { PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt } from "./paste-tokens.js";
9
- export { AttachmentCard, BusySendButton, ChatComposer, ComposerAttachmentButton, ComposerHint, ConversationAvatar, ConversationSurface, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, ThreadPanel, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt };
10
+ export { AttachmentCard, BusySendButton, ChatComposer, ComposerAttachmentButton, ComposerHint, ConversationAvatar, ConversationSurface, MessageBody, MessageComposer, MessageDayDivider, MessageGroup, MessageRow, PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, ThreadPanel, TranscriptComposition, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt, transcriptDisclosureKey };
@@ -0,0 +1,66 @@
1
+ import type { Component, ComponentProps } from 'solid-js';
2
+ import { type TranscriptRow } from './transcript-grouping';
3
+ import { type TranscriptFoldMode, type TranscriptMounting } from './transcript-folding';
4
+ export type TranscriptFoldState = 'default' | 'collapsed' | 'expanded';
5
+ export type TranscriptDisplayKind = 'single' | 'group' | 'turn';
6
+ export type { GroupedTranscript, TranscriptDisplayItem, TranscriptRow, TranscriptTurnItem, } from './transcript-grouping';
7
+ export type { TranscriptFoldMode, TranscriptFoldPlan, TranscriptMounting, } from './transcript-folding';
8
+ export type TranscriptCompositionFold = Readonly<{
9
+ /** Stable host persistence key. Use with `transcriptDisclosureKey` to seed a store. */
10
+ key: string;
11
+ mode: TranscriptFoldMode;
12
+ mounting: TranscriptMounting;
13
+ /** Distinct host-classified request IDs in this contiguous fold segment. */
14
+ callCount: number;
15
+ }>;
16
+ export type TranscriptCompositionRowContext = Readonly<{
17
+ /** Original index in the caller's row array, including rows hidden by the host. */
18
+ index: number;
19
+ /** The row's current position in the generic display composition. */
20
+ displayKind: TranscriptDisplayKind;
21
+ group?: Readonly<{
22
+ index: number;
23
+ size: number;
24
+ }>;
25
+ turn?: Readonly<{
26
+ /** Derived from the first stable row ID; presentation bookkeeping only. */
27
+ id: string;
28
+ /** A presentation state, never proof of successful runtime completion. */
29
+ complete: boolean;
30
+ interim: boolean;
31
+ }>;
32
+ fold?: TranscriptCompositionFold;
33
+ }>;
34
+ export type TranscriptCompositionRendererProps<T> = Readonly<{
35
+ /** Reactive Solid prop; a row component keeps its DOM owner stable as values update. */
36
+ row: TranscriptRow<T>;
37
+ context: TranscriptCompositionRowContext;
38
+ }>;
39
+ export type TranscriptCompositionProps<T> = Omit<ComponentProps<'div'>, 'children'> & {
40
+ /** Ordered, already redacted rows with host-supplied semantic classifications. */
41
+ rows: readonly TranscriptRow<T>[];
42
+ /** Render one host row. Permission, session, event and protocol authority stay outside this component. */
43
+ renderRow: Component<TranscriptCompositionRendererProps<T>>;
44
+ /** The host's current run state. Live rows stay mounted and expanded until settled. */
45
+ running?: boolean;
46
+ /** Reset scope for row identity and disclosure. It is not inferred from display content. */
47
+ resetKey?: string | number | null;
48
+ /** Default settled fold policy. `tools` keeps prose visible; `prose` retains a proven conclusion. */
49
+ foldMode?: TranscriptFoldMode;
50
+ /** Global command state. `collapsed` keeps hidden content mounted; prose folds only before a host-marked conclusion. */
51
+ foldState?: TranscriptFoldState;
52
+ onFoldStateChange?: (state: TranscriptFoldState) => void;
53
+ /** Optional controlled disclosure map keyed by `transcriptDisclosureKey`. */
54
+ disclosure?: ReadonlyMap<string, boolean>;
55
+ onDisclosureChange?: (key: string, expanded: boolean) => void;
56
+ };
57
+ /** Stable, transcript-scoped key for host-owned disclosure restoration. */
58
+ export declare function transcriptDisclosureKey(resetKey: string | number | null | undefined, mode: TranscriptFoldMode, firstRowId: string): string;
59
+ /**
60
+ * Compose host-classified rows into stable turns, contiguous groups and disclosures.
61
+ * Row render owners are keyed by `id` in a single list, so loose rows keep their DOM
62
+ * owners when the grouping threshold promotes them into a turn. The caller owns row
63
+ * identity, protocol meaning, redaction, permissions and durable conversation state.
64
+ */
65
+ export declare function TranscriptComposition<T>(props: TranscriptCompositionProps<T>): import("solid-js").JSX.Element;
66
+ //# sourceMappingURL=transcript-composition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-composition.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/transcript-composition.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,395 @@
1
+ import { cn } from "../../lib/utils.js";
2
+ import { Button } from "../ui/button/button.js";
3
+ import { applyTranscriptRunningState, createTranscriptGrouper } from "./transcript-grouping.js";
4
+ import { planTranscriptFold } from "./transcript-folding.js";
5
+ import { createComponent, effect, insert, memo, mergeProps, setAttribute, spread, template } from "solid-js/web";
6
+ import { ChevronDown } from "lucide-solid";
7
+ import { For, Show, createComponent as createComponent$1, createMemo, createSignal, createUniqueId, splitProps } from "solid-js";
8
+ //#region src/components/conversation/transcript-composition.tsx
9
+ var _tmpl$ = /*#__PURE__*/ template(`<div data-slot=transcript-row class="flex min-w-0 flex-col gap-1"><div data-slot=transcript-row-content>`);
10
+ var _tmpl$2 = /*#__PURE__*/ template(`<span>`);
11
+ var _tmpl$3 = /*#__PURE__*/ template(`<div class="flex justify-end">`);
12
+ var _tmpl$4 = /*#__PURE__*/ template(`<div data-slot=transcript-composition><div data-slot=transcript-rows class="flex min-w-0 flex-col gap-1">`);
13
+ var encodeId = (value) => encodeURIComponent(value);
14
+ /** Stable, transcript-scoped key for host-owned disclosure restoration. */
15
+ function transcriptDisclosureKey(resetKey, mode, firstRowId) {
16
+ return `transcript:${encodeId(String(resetKey ?? ""))}:${mode}:${encodeId(firstRowId)}`;
17
+ }
18
+ function panelId(instanceId, resetKey, rowId) {
19
+ return `${instanceId}-row-${encodeId(resetKey)}-${encodeId(rowId)}`;
20
+ }
21
+ function buttonId(instanceId, resetKey, segmentKey) {
22
+ return `${instanceId}-fold-${encodeId(resetKey)}-${encodeId(segmentKey)}`;
23
+ }
24
+ function sameGroup(a, b) {
25
+ return a === b || !!a && !!b && a.index === b.index && a.size === b.size;
26
+ }
27
+ function sameTurn(a, b) {
28
+ return a === b || !!a && !!b && a.id === b.id && a.complete === b.complete && a.interim === b.interim;
29
+ }
30
+ function sameFold(a, b) {
31
+ return a === b || !!a && !!b && a.key === b.key && a.mode === b.mode && a.mounting === b.mounting && a.callCount === b.callCount;
32
+ }
33
+ function sameFoldGroup(a, b) {
34
+ return a === b || !!a && !!b && a.key === b.key && a.callCount === b.callCount && a.rows.length === b.rows.length && a.rows.every((row, index) => row === b.rows[index]);
35
+ }
36
+ function sameRecord(a, b) {
37
+ return a.row === b.row && a.index === b.index && a.displayKind === b.displayKind && sameGroup(a.group, b.group) && sameTurn(a.turn, b.turn) && sameFold(a.fold, b.fold) && sameFoldGroup(a.foldControl, b.foldControl);
38
+ }
39
+ function firstRow(item) {
40
+ return item.kind === "single" ? item.row : item.rows[0];
41
+ }
42
+ function appendTurnItem(item, displayKind, turn, indices, into) {
43
+ if (item.kind === "single") {
44
+ into.push({
45
+ row: item.row,
46
+ index: indices.get(item.row) ?? item.index,
47
+ displayKind,
48
+ turn
49
+ });
50
+ return;
51
+ }
52
+ item.rows.forEach((row, index) => {
53
+ into.push({
54
+ row,
55
+ index: indices.get(row) ?? item.startIndex + index,
56
+ displayKind,
57
+ group: {
58
+ index,
59
+ size: item.rows.length
60
+ },
61
+ turn
62
+ });
63
+ });
64
+ }
65
+ function appendDisplayItem(item, indices, into) {
66
+ if (item.kind !== "turn") {
67
+ appendTurnItem(item, item.kind, void 0, indices, into);
68
+ return;
69
+ }
70
+ const turn = {
71
+ id: item.items.map(firstRow).find((row) => row)?.id ?? "empty-turn",
72
+ complete: item.complete,
73
+ interim: !!item.interim
74
+ };
75
+ for (const child of item.items) appendTurnItem(child, "turn", turn, indices, into);
76
+ }
77
+ function buildDisclosures(records, defaultMode, foldState, resetKey) {
78
+ const controls = [];
79
+ let scope = [];
80
+ const flushScope = () => {
81
+ if (!scope.length) return;
82
+ const rows = scope.map((record) => record.row);
83
+ const hasInterimTurn = scope.some((record) => record.turn?.interim);
84
+ const hasConclusion = scope.some((record) => record.row.conclusion);
85
+ const mode = hasInterimTurn ? "interim" : foldState === "default" ? defaultMode : foldState === "collapsed" ? hasConclusion ? "prose" : "tools" : "interim";
86
+ const plan = planTranscriptFold(rows, {
87
+ mode,
88
+ settled: true
89
+ });
90
+ const foldable = plan.entries.flatMap((entry, index) => entry.mounting === "always" ? [] : [{
91
+ record: scope[index],
92
+ mounting: entry.mounting
93
+ }]);
94
+ if (foldable.length) {
95
+ const control = {
96
+ key: transcriptDisclosureKey(resetKey, mode, scope[0]?.row.id ?? foldable[0].record.row.id),
97
+ mode,
98
+ callCount: plan.callCount,
99
+ rows: foldable.map(({ record }) => record.row)
100
+ };
101
+ controls.push(control);
102
+ foldable.forEach(({ record, mounting }, index) => {
103
+ record.fold = {
104
+ key: control.key,
105
+ mode: control.mode,
106
+ mounting: foldState === "collapsed" ? "hide-when-collapsed" : mounting,
107
+ callCount: control.callCount
108
+ };
109
+ if (index === 0) record.foldControl = control;
110
+ });
111
+ }
112
+ scope = [];
113
+ };
114
+ for (const record of records) {
115
+ if (record.row.opensTurn && scope.length) flushScope();
116
+ scope.push(record);
117
+ }
118
+ flushScope();
119
+ return controls;
120
+ }
121
+ function foldLabel(mode, count, expanded) {
122
+ const subject = mode === "tools" ? count === 1 ? "1 tool call" : count > 1 ? `${count} tool calls` : "tool details" : mode === "prose" ? "earlier activity" : "interim activity";
123
+ return `${expanded ? "Hide" : "Show"} ${subject}`;
124
+ }
125
+ function TranscriptRowEntry(props) {
126
+ const current = props.record;
127
+ const id = () => panelId(props.prefix, props.resetKey(), current().row.id);
128
+ const foldControl = () => current().foldControl;
129
+ const disclosureKey = () => current().fold?.key;
130
+ const expanded = () => {
131
+ const key = disclosureKey();
132
+ return key ? props.expanded(key)() : true;
133
+ };
134
+ const collapsed = () => !!current().fold && !expanded();
135
+ const disabled = () => props.running() || props.foldState() !== "default";
136
+ const context = () => ({
137
+ index: current().index,
138
+ displayKind: current().displayKind,
139
+ group: current().group,
140
+ turn: current().turn,
141
+ fold: current().fold
142
+ });
143
+ const groupIndex = () => current().group?.index;
144
+ const groupSize = () => current().group?.size;
145
+ const renderable = () => current().fold?.mounting !== "unmount-when-collapsed" || !collapsed();
146
+ const renderHostRow = () => createComponent$1(props.renderRow, {
147
+ get row() {
148
+ return current().row;
149
+ },
150
+ get context() {
151
+ return context();
152
+ }
153
+ });
154
+ const controlLabel = () => {
155
+ const control = foldControl();
156
+ return control ? foldLabel(control.mode, control.callCount, expanded()) : "";
157
+ };
158
+ const controlRows = () => foldControl()?.rows.map((row) => panelId(props.prefix, props.resetKey(), row.id)).join(" ");
159
+ return (() => {
160
+ var _el$ = _tmpl$(), _el$2 = _el$.firstChild;
161
+ insert(_el$, createComponent(Show, {
162
+ get when() {
163
+ return foldControl();
164
+ },
165
+ children: (control) => createComponent(Button, {
166
+ get id() {
167
+ return buttonId(props.prefix, props.resetKey(), control().key);
168
+ },
169
+ type: "button",
170
+ variant: "ghost",
171
+ size: "sm",
172
+ "class": "w-full justify-start",
173
+ get ["aria-label"]() {
174
+ return controlLabel();
175
+ },
176
+ get ["aria-controls"]() {
177
+ return controlRows();
178
+ },
179
+ get ["aria-expanded"]() {
180
+ return expanded();
181
+ },
182
+ get ["aria-disabled"]() {
183
+ return disabled() || void 0;
184
+ },
185
+ onClick: () => props.onToggle(control().key, !expanded()),
186
+ get children() {
187
+ return [createComponent(ChevronDown, {
188
+ "aria-hidden": "true",
189
+ "class": "size-4 shrink-0"
190
+ }), (() => {
191
+ var _el$3 = _tmpl$2();
192
+ insert(_el$3, controlLabel);
193
+ return _el$3;
194
+ })()];
195
+ }
196
+ })
197
+ }), _el$2);
198
+ insert(_el$2, createComponent(Show, {
199
+ get when() {
200
+ return memo(() => !!renderable())() ? current() : void 0;
201
+ },
202
+ children: (record) => {
203
+ record();
204
+ return renderHostRow();
205
+ }
206
+ }));
207
+ effect((_p$) => {
208
+ var _v$ = current().displayKind, _v$2 = groupIndex(), _v$3 = groupSize(), _v$4 = id(), _v$5 = collapsed(), _v$6 = collapsed() || void 0;
209
+ _v$ !== _p$.e && setAttribute(_el$, "data-display-kind", _p$.e = _v$);
210
+ _v$2 !== _p$.t && setAttribute(_el$, "data-group-index", _p$.t = _v$2);
211
+ _v$3 !== _p$.a && setAttribute(_el$, "data-group-size", _p$.a = _v$3);
212
+ _v$4 !== _p$.o && setAttribute(_el$2, "id", _p$.o = _v$4);
213
+ _v$5 !== _p$.i && (_el$2.hidden = _p$.i = _v$5);
214
+ _v$6 !== _p$.n && setAttribute(_el$2, "aria-hidden", _p$.n = _v$6);
215
+ return _p$;
216
+ }, {
217
+ e: void 0,
218
+ t: void 0,
219
+ a: void 0,
220
+ o: void 0,
221
+ i: void 0,
222
+ n: void 0
223
+ });
224
+ return _el$;
225
+ })();
226
+ }
227
+ /**
228
+ * Compose host-classified rows into stable turns, contiguous groups and disclosures.
229
+ * Row render owners are keyed by `id` in a single list, so loose rows keep their DOM
230
+ * owners when the grouping threshold promotes them into a turn. The caller owns row
231
+ * identity, protocol meaning, redaction, permissions and durable conversation state.
232
+ */
233
+ function TranscriptComposition(props) {
234
+ const [local, rest] = splitProps(props, [
235
+ "class",
236
+ "rows",
237
+ "renderRow",
238
+ "running",
239
+ "resetKey",
240
+ "foldMode",
241
+ "foldState",
242
+ "onFoldStateChange",
243
+ "disclosure",
244
+ "onDisclosureChange"
245
+ ]);
246
+ const instanceId = createUniqueId();
247
+ const grouper = createTranscriptGrouper();
248
+ const [internalFoldState, setInternalFoldState] = createSignal("default");
249
+ const rowHandles = /* @__PURE__ */ new Map();
250
+ const disclosureSignals = /* @__PURE__ */ new Map();
251
+ let lastResetKey = String(local.resetKey ?? "");
252
+ const resetKey = () => String(local.resetKey ?? "");
253
+ const foldState = () => local.foldState ?? internalFoldState();
254
+ const running = () => !!local.running;
255
+ const localDisclosure = (key) => {
256
+ let signal = disclosureSignals.get(key);
257
+ if (!signal) {
258
+ const [get, set] = createSignal(void 0);
259
+ signal = {
260
+ get,
261
+ set
262
+ };
263
+ disclosureSignals.set(key, signal);
264
+ }
265
+ return signal;
266
+ };
267
+ const disclosureExpanded = (key) => {
268
+ const signal = localDisclosure(key);
269
+ return () => {
270
+ if (running()) return true;
271
+ if (foldState() === "collapsed") return false;
272
+ if (foldState() === "expanded") return true;
273
+ return local.disclosure?.get(key) ?? signal.get() ?? false;
274
+ };
275
+ };
276
+ const grouped = createMemo(() => grouper(local.rows));
277
+ const layout = createMemo(() => {
278
+ const identity = resetKey();
279
+ if (identity !== lastResetKey) {
280
+ rowHandles.clear();
281
+ disclosureSignals.clear();
282
+ lastResetKey = identity;
283
+ }
284
+ const indexByRow = new Map(local.rows.map((row, index) => [row, index]));
285
+ const display = applyTranscriptRunningState(grouped(), running());
286
+ const records = [];
287
+ display.forEach((item) => appendDisplayItem(item, indexByRow, records));
288
+ return {
289
+ rows: records,
290
+ controls: buildDisclosures(records, local.foldMode ?? "tools", foldState(), identity)
291
+ };
292
+ });
293
+ const handles = createMemo(() => {
294
+ const live = /* @__PURE__ */ new Set();
295
+ const identity = resetKey();
296
+ const result = layout().rows.map((record) => {
297
+ const key = `${identity}\u001f${record.row.id}`;
298
+ live.add(key);
299
+ let handle = rowHandles.get(key);
300
+ if (!handle) {
301
+ const [get, set] = createSignal(record);
302
+ handle = {
303
+ key,
304
+ get,
305
+ set,
306
+ last: record
307
+ };
308
+ rowHandles.set(key, handle);
309
+ } else if (!sameRecord(handle.last, record)) {
310
+ handle.last = record;
311
+ handle.set(record);
312
+ }
313
+ return handle;
314
+ });
315
+ for (const key of rowHandles.keys()) if (!live.has(key)) rowHandles.delete(key);
316
+ return result;
317
+ });
318
+ const allControlIds = () => layout().controls.flatMap((control) => control.rows.map((row) => panelId(instanceId, resetKey(), row.id))).join(" ");
319
+ const updateFoldState = () => {
320
+ if (running()) return;
321
+ const current = foldState();
322
+ const next = current === "default" ? "collapsed" : current === "collapsed" ? "expanded" : "default";
323
+ if (local.foldState === void 0) setInternalFoldState(next);
324
+ local.onFoldStateChange?.(next);
325
+ };
326
+ const updateDisclosure = (key, expanded) => {
327
+ if (running() || foldState() !== "default") return;
328
+ if (local.disclosure) {
329
+ local.onDisclosureChange?.(key, expanded);
330
+ return;
331
+ }
332
+ localDisclosure(key).set(expanded);
333
+ local.onDisclosureChange?.(key, expanded);
334
+ };
335
+ return (() => {
336
+ var _el$4 = _tmpl$4(), _el$6 = _el$4.firstChild;
337
+ spread(_el$4, mergeProps({
338
+ get ["data-fold-state"]() {
339
+ return foldState();
340
+ },
341
+ get ["class"]() {
342
+ return cn("flex w-full min-w-0 flex-col gap-1", local.class);
343
+ }
344
+ }, rest), false, true);
345
+ insert(_el$4, createComponent(Show, {
346
+ get when() {
347
+ return local.onFoldStateChange;
348
+ },
349
+ get children() {
350
+ var _el$5 = _tmpl$3();
351
+ insert(_el$5, createComponent(Button, {
352
+ id: `${instanceId}-fold-all`,
353
+ type: "button",
354
+ variant: "ghost",
355
+ size: "sm",
356
+ get ["aria-controls"]() {
357
+ return allControlIds() || void 0;
358
+ },
359
+ get ["aria-disabled"]() {
360
+ return running() || void 0;
361
+ },
362
+ onClick: updateFoldState,
363
+ get children() {
364
+ return memo(() => foldState() === "default")() ? "Collapse all activity" : foldState() === "collapsed" ? "Expand all activity" : "Restore default folding";
365
+ }
366
+ }));
367
+ return _el$5;
368
+ }
369
+ }), _el$6);
370
+ insert(_el$6, createComponent(For, {
371
+ get each() {
372
+ return handles();
373
+ },
374
+ children: (handle) => createComponent(TranscriptRowEntry, {
375
+ get record() {
376
+ return handle.get;
377
+ },
378
+ get renderRow() {
379
+ return local.renderRow;
380
+ },
381
+ prefix: instanceId,
382
+ resetKey,
383
+ expanded: disclosureExpanded,
384
+ running,
385
+ foldState,
386
+ onToggle: updateDisclosure
387
+ })
388
+ }));
389
+ return _el$4;
390
+ })();
391
+ }
392
+ //#endregion
393
+ export { TranscriptComposition, transcriptDisclosureKey };
394
+
395
+ //# sourceMappingURL=transcript-composition.js.map