@syntrologie/adapt-chatbot 2.26.0 → 2.28.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 (99) hide show
  1. package/dist/AdaptiveChatBar.d.ts +76 -0
  2. package/dist/AdaptiveChatBar.d.ts.map +1 -0
  3. package/dist/AdaptiveChatBar.js +10 -0
  4. package/dist/AdaptiveChatBar.js.map +7 -0
  5. package/dist/AdaptiveChatBarMountable.d.ts +35 -0
  6. package/dist/AdaptiveChatBarMountable.d.ts.map +1 -0
  7. package/dist/AdaptiveChatTrail.d.ts +77 -0
  8. package/dist/AdaptiveChatTrail.d.ts.map +1 -0
  9. package/dist/AdaptiveChatTrail.js +9 -0
  10. package/dist/AdaptiveChatTrail.js.map +7 -0
  11. package/dist/AdaptiveChipsStrip.d.ts +1150 -0
  12. package/dist/AdaptiveChipsStrip.d.ts.map +1 -0
  13. package/dist/AdaptiveChipsStrip.js +11 -0
  14. package/dist/AdaptiveChipsStrip.js.map +7 -0
  15. package/dist/AdaptiveChipsStripMountable.d.ts +24 -0
  16. package/dist/AdaptiveChipsStripMountable.d.ts.map +1 -0
  17. package/dist/ChatAssistantLit.d.ts +23 -21
  18. package/dist/ChatAssistantLit.d.ts.map +1 -1
  19. package/dist/ChatAssistantLit.js +5 -3
  20. package/dist/ChatSession.d.ts +178 -0
  21. package/dist/ChatSession.d.ts.map +1 -0
  22. package/dist/ChatTransport.d.ts +283 -0
  23. package/dist/ChatTransport.d.ts.map +1 -0
  24. package/dist/NavLinkMountable.d.ts +25 -0
  25. package/dist/NavLinkMountable.d.ts.map +1 -0
  26. package/dist/NavLinkMountable.test.d.ts +2 -0
  27. package/dist/NavLinkMountable.test.d.ts.map +1 -0
  28. package/dist/TextAnswerMountable.d.ts +17 -0
  29. package/dist/TextAnswerMountable.d.ts.map +1 -0
  30. package/dist/Turnstile.d.ts +83 -0
  31. package/dist/Turnstile.d.ts.map +1 -0
  32. package/dist/chunk-435KJD27.js +192 -0
  33. package/dist/chunk-435KJD27.js.map +7 -0
  34. package/dist/chunk-AUER7ZCK.js +634 -0
  35. package/dist/chunk-AUER7ZCK.js.map +7 -0
  36. package/dist/chunk-DOMEUJR7.js +382 -0
  37. package/dist/chunk-DOMEUJR7.js.map +7 -0
  38. package/dist/{chunk-O7RWNUVU.js → chunk-KUO67E2W.js} +1573 -4079
  39. package/dist/chunk-KUO67E2W.js.map +7 -0
  40. package/dist/chunk-QELVKBQV.js +214 -0
  41. package/dist/chunk-QELVKBQV.js.map +7 -0
  42. package/dist/chunk-UC4XU6GH.js +3306 -0
  43. package/dist/chunk-UC4XU6GH.js.map +7 -0
  44. package/dist/elements/ActionHandler.d.ts +34 -0
  45. package/dist/elements/ActionHandler.d.ts.map +1 -0
  46. package/dist/elements/ElementInstanceStore.d.ts +155 -0
  47. package/dist/elements/ElementInstanceStore.d.ts.map +1 -0
  48. package/dist/elements/ElementInstanceStore.test.d.ts +2 -0
  49. package/dist/elements/ElementInstanceStore.test.d.ts.map +1 -0
  50. package/dist/elements/ElementTypeHandler.d.ts +77 -0
  51. package/dist/elements/ElementTypeHandler.d.ts.map +1 -0
  52. package/dist/elements/ItemHandler.d.ts +60 -0
  53. package/dist/elements/ItemHandler.d.ts.map +1 -0
  54. package/dist/elements/ItemHandler.test.d.ts +2 -0
  55. package/dist/elements/ItemHandler.test.d.ts.map +1 -0
  56. package/dist/elements/TileHandler.d.ts +52 -0
  57. package/dist/elements/TileHandler.d.ts.map +1 -0
  58. package/dist/elements/blockRenderer.d.ts +46 -0
  59. package/dist/elements/blockRenderer.d.ts.map +1 -0
  60. package/dist/elements/blockRenderer.test.d.ts +13 -0
  61. package/dist/elements/blockRenderer.test.d.ts.map +1 -0
  62. package/dist/elements/blocks.d.ts +58 -0
  63. package/dist/elements/blocks.d.ts.map +1 -0
  64. package/dist/elements/envelope.d.ts +24 -0
  65. package/dist/elements/envelope.d.ts.map +1 -0
  66. package/dist/elements/fetcher.d.ts +40 -0
  67. package/dist/elements/fetcher.d.ts.map +1 -0
  68. package/dist/elements/index.d.ts +32 -0
  69. package/dist/elements/index.d.ts.map +1 -0
  70. package/dist/elements/types.d.ts +106 -0
  71. package/dist/elements/types.d.ts.map +1 -0
  72. package/dist/observer/__tests__/allowlist.test.d.ts +9 -0
  73. package/dist/observer/__tests__/allowlist.test.d.ts.map +1 -0
  74. package/dist/observer/__tests__/observer-isolation.test.d.ts +13 -0
  75. package/dist/observer/__tests__/observer-isolation.test.d.ts.map +1 -0
  76. package/dist/observer/__tests__/queue.test.d.ts +2 -0
  77. package/dist/observer/__tests__/queue.test.d.ts.map +1 -0
  78. package/dist/observer/__tests__/transport.test.d.ts +2 -0
  79. package/dist/observer/__tests__/transport.test.d.ts.map +1 -0
  80. package/dist/observer/allowlist.d.ts +32 -0
  81. package/dist/observer/allowlist.d.ts.map +1 -0
  82. package/dist/observer/index.d.ts +35 -0
  83. package/dist/observer/index.d.ts.map +1 -0
  84. package/dist/observer/queue.d.ts +57 -0
  85. package/dist/observer/queue.d.ts.map +1 -0
  86. package/dist/observer/transport.d.ts +26 -0
  87. package/dist/observer/transport.d.ts.map +1 -0
  88. package/dist/runtime.d.ts +7 -0
  89. package/dist/runtime.d.ts.map +1 -1
  90. package/dist/runtime.js +1617 -2
  91. package/dist/runtime.js.map +4 -4
  92. package/dist/schema.d.ts +3120 -7
  93. package/dist/schema.d.ts.map +1 -1
  94. package/dist/schema.js +40 -0
  95. package/dist/schema.js.map +2 -2
  96. package/dist/types.d.ts +30 -2
  97. package/dist/types.d.ts.map +1 -1
  98. package/package.json +13 -1
  99. package/dist/chunk-O7RWNUVU.js.map +0 -7
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/AdaptiveChipsStrip.ts"],
4
+ "sourcesContent": ["/**\n * AdaptiveChipsStrip \u2014 the suggested-chips tile kind.\n *\n * A horizontally scrollable strip of chips. Each chip carries a title\n * and a typed payload. Clicking a chip mounts its payload widget in a\n * chromed drawer directly below the strip; clicking again (or the\n * payload's close button) collapses it. Only one chip is open at a\n * time per strip.\n *\n * Payload rendering goes through the runtime widget registry \u2014 the\n * payload is a `{ widget, props? }` reference (same shape as\n * `slots.drawer.lid`). Click \u2192 strip looks the widget id up in\n * `runtime.widgets`, mounts it into the drawer body, unmounts on\n * close. No resolver map, no per-customer JS \u2014 the same registry\n * that mounts tile widgets handles chip payloads.\n *\n * Built-in payload widgets (registered by this adaptive):\n * - `adaptive-chatbot:text-answer` \u2014 single paragraph of text\n *\n * Customers ship their own widgets through the standard adaptive\n * registration path (each adaptive's `runtime.widgets` list). LLM-\n * authored configs reference them by id; the widget owns its props\n * schema. No new vocabulary per chip \u2014 same as every other widget\n * in the SDK.\n *\n * Light DOM. The strip itself is chromeless per the PRD; the payload\n * drawer below is *always* chromed (PRD \u00A76.7) \u2014 the chromed boundary\n * is what visually links the open chip to its content.\n *\n * See PRD \u00A74.5 (the suggested-chips pattern) for the canonical UX.\n */\n\nimport { AuthoringFieldsZ, NotifyZ, TriggerWhenZ } from '@syntrologie/sdk-contracts';\nimport { html, LitElement, nothing, type TemplateResult } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { z } from 'zod';\n\n/**\n * Structural type for just the runtime surfaces this strip uses.\n * Avoids a hard dependency on @syntrologie/runtime-sdk while staying\n * compatible with the real `SmartCanvasRuntime` shape.\n *\n * `events.subscribe` is optional \u2014 present when running inside the full\n * canvas runtime where the chips strip wants to receive LLM-authored\n * chips via `element.compositional_append`. Absent in standalone test\n * harnesses; the strip falls back to hand-authored chips only.\n */\ninterface ChipsStripRuntime {\n widgets: {\n has: (id: string) => boolean;\n mount: (\n id: string,\n container: HTMLElement,\n props?: Record<string, unknown>\n ) => { unmount: () => void; update?: (props?: Record<string, unknown>) => void };\n };\n events?: {\n /** Used to ping the store with a replay request when the chip strip\n * (re)mounts \u2014 see `_subscribeCompositional`. */\n publish?: (name: string, props?: Record<string, unknown>) => void;\n subscribe?: (\n handler: (event: { name: string; props?: Record<string, unknown> }) => void\n ) => () => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Schema \u2014 `suggestions:chip` follows the compositional-action pattern\n// used by `faq:question`. Each chip is a discriminated-union action\n// envelope (`kind`, `config`, optional `triggerWhen` + `notify`) so it\n// inherits the SDK's validation, conditional-gating, authoring-metadata,\n// and LLM-authoring vocabulary for free.\n//\n// The chip's `payload` is a widget reference \u2014 same `{ widget, props }`\n// shape used by `slots.drawer.lid` and tile configs. The strip mounts\n// the widget via the runtime widget registry. LLMs author payloads by\n// picking from the catalog of registered widget ids \u2014 same closed\n// vocabulary they use everywhere else in the SDK. No per-customer JS,\n// no resolver maps.\n// ---------------------------------------------------------------------------\n\nconst ChipPayloadZ = z\n .object({\n widget: z\n .string()\n .min(1)\n .describe(\n \"Widget id from the runtime registry \u2014 same vocabulary as a tile's `widget` field. Click \u2192 strip mounts this widget into the chip drawer. Built-ins shipped by adaptive-chatbot: `adaptive-chatbot:text-answer`. Other adaptives register their own (e.g. `adaptive-faq:question-card`, `adaptive-product:mini`).\"\n ),\n props: z\n .record(z.unknown())\n .optional()\n .describe('Props passed to the mounted widget. The widget owns its own props schema.'),\n })\n .strict()\n .describe(\n \"The content that mounts in the chip's drawer when it opens. Same { widget, props } shape used by slots.lid and tile configs \u2014 chips are tiles by another name.\"\n );\n\n/**\n * Schema for a single suggestion chip (compositional action).\n *\n * Mirrors the `faq:question` pattern: discriminated by `kind`, body in\n * `config`, optional `triggerWhen` for per-chip conditional visibility,\n * optional `notify` for a toast when the chip first becomes relevant,\n * plus the standard `AuthoringFieldsZ` (id/title/description/validation \u2014\n * stripped server-side before serving to the runtime).\n */\nexport const SuggestionChipSchema = z\n .object({\n ...AuthoringFieldsZ,\n kind: z\n .literal('suggestions:chip')\n .describe(\n 'Compositional action type for a single chip in the AdaptiveChipsStrip. Rendered by the strip widget; not executed by the runtime action engine.'\n ),\n config: z\n .object({\n id: z.string().min(1).describe('Stable identity for keyed rendering and event payloads.'),\n title: z\n .string()\n .min(1)\n .describe('Chip label \u2014 single line, ~32ch typical, ellipsis-truncates beyond.'),\n payload: ChipPayloadZ,\n })\n .describe(\n 'Per-chip configuration: identity, label, and the payload that opens in the drawer.'\n ),\n triggerWhen: TriggerWhenZ.describe(\n 'Conditional visibility strategy. When null or omitted, the chip is always shown. Use a rules strategy to show/hide based on page URL, event counts, or session metrics \u2014 same evaluator that powers tile activation.'\n ),\n notify: NotifyZ.describe(\n 'Toast notification shown when triggerWhen transitions false \u2192 true. Required when triggerWhen is set \u2014 pass null to opt out of the toast.'\n ),\n })\n .refine((data) => !data.triggerWhen || data.notify !== undefined, {\n message:\n 'notify is required when triggerWhen is present (use null to opt out of notifications)',\n })\n .describe(\n 'A single suggestion chip in the chips strip. Same authoring shape as faq:question \u2014 discriminator + config body + optional conditional gating + optional proactive notification.'\n );\n\n// ---------------------------------------------------------------------------\n// Types (inferred from schema \u2014 single source of truth)\n// ---------------------------------------------------------------------------\n\n/** A widget-reference payload that mounts in the drawer when its chip is opened. */\nexport type ChipPayload = z.infer<typeof ChipPayloadZ>;\n\n/** Declarative chip data. Inferred from `SuggestionChipSchema`. */\nexport type SuggestionChip = z.infer<typeof SuggestionChipSchema>;\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveChipsStrip extends LitElement {\n static override properties = {\n chips: { attribute: false },\n runtimeRef: { attribute: false },\n chromeless: { type: Boolean, reflect: true },\n _openId: { state: true },\n _canScrollLeft: { state: true },\n _canScrollRight: { state: true },\n };\n\n chips: SuggestionChip[] = [];\n /**\n * Runtime reference \u2014 provides `widgets.has(id)` and\n * `widgets.mount(id, container, props)` so the strip can render\n * chip payloads through the same registry that mounts tile widgets.\n * Null until the host wires it (the AdaptiveChipsStripMountable\n * passes it in from the runtime).\n */\n runtimeRef: ChipsStripRuntime | null = null;\n /**\n * Strip-level chrome toggle \u2014 applies uniformly to all chips +\n * the payload drawer. Resolved upstream from `tile.chromeless`\n * (which itself defaults from `slot.theme.chromeless`). Per-chip\n * inconsistency would break the mini-canvas visual rhythm so\n * there's no per-chip flag \u2014 one strip, one style.\n */\n chromeless = false;\n _openId: string | null = null;\n /**\n * Horizontal-scroll affordance state. The strip is a single row that\n * overflows horizontally when there are more chips than the panel\n * width can hold; the left/right chevron buttons are visible only\n * when the user can actually scroll in that direction. ResizeObserver\n * + scroll listener keep these in sync.\n */\n _canScrollLeft = false;\n _canScrollRight = false;\n private _stripResizeObserver: ResizeObserver | null = null;\n /** Unsubscribe handle for the compositional-append/patch/remove event\n * subscription on `runtime.events`. Null when the strip hasn't yet\n * resolved its tile id (no parent container yet) or the runtime\n * doesn't expose an event bus. */\n private _compositionalUnsub: (() => void) | null = null;\n /** Tile id this strip occupies. Resolved on first `updated()` pass by\n * walking the DOM ancestor chain for `data-tile-id`. Cached because\n * the lookup is stable for the lifetime of the mount. Null while\n * unresolved (or when not running inside a SyntroTileCard, e.g.\n * in standalone test fixtures). */\n private _tileId: string | null = null;\n /** Instance ids of chips appended by LLM mounts (vs. authored chip\n * configs from `props.chips`). Tracked so `removeItem(instance_id)`\n * can find and prune by id. */\n private _llmAppendedIds: Set<string> = new Set();\n\n /**\n * Tracks the currently-mounted payload widget so we can swap on chip\n * change + unmount on close. Same lifecycle pattern as\n * `SyntroCanvasOverlay._lidMountHandle`.\n */\n private _payloadMount: {\n chipId: string;\n widgetId: string;\n handle: { unmount: () => void; update?: (props?: Record<string, unknown>) => void };\n } | null = null;\n\n override createRenderRoot(): HTMLElement {\n return this;\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._unmountPayload();\n this._stripResizeObserver?.disconnect();\n this._stripResizeObserver = null;\n this._compositionalUnsub?.();\n this._compositionalUnsub = null;\n }\n\n override updated(changed: Map<string, unknown>): void {\n super.updated(changed);\n // Reconcile the payload mount whenever the open chip, the chip\n // data (props could have changed), or the runtime changes.\n if (changed.has('_openId') || changed.has('chips') || changed.has('runtimeRef')) {\n this._syncPayloadMount();\n }\n // Wire scroll affordances every render in case the strip DOM was\n // recreated. Idempotent: re-attaching to the same node is a no-op.\n this._syncStripObserver();\n this._updateScrollAffordances();\n // Re-resolve the tile id + (re)attach the compositional-append\n // subscription whenever the runtime ref changes. The tile id is\n // looked up from the closest `[data-tile-id]` ancestor \u2014 set by\n // SyntroTileCard's render.\n if (changed.has('runtimeRef')) {\n this._compositionalUnsub?.();\n this._compositionalUnsub = null;\n this._tileId = this._resolveTileId();\n }\n // Lazy-subscribe: the runtime may not have `events.subscribe`\n // resolved at first `updated()` pass (the chips strip mounts before\n // the runtime finishes wiring its event bus). Re-attempt on every\n // updated() pass while we're still unsubscribed \u2014 idempotent once\n // the subscription lands.\n if (!this._compositionalUnsub && this._tileId == null) {\n this._tileId = this._resolveTileId();\n }\n if (!this._compositionalUnsub) {\n this._subscribeCompositional();\n }\n }\n\n private _resolveTileId(): string | null {\n const anchor = this.closest<HTMLElement>('[data-tile-id]');\n return anchor?.getAttribute('data-tile-id') ?? null;\n }\n\n /**\n * Subscribe to the runtime's event bus for compositional-item events\n * targeting this strip's tile id. The subscription survives until\n * disconnect; when an event arrives with a non-matching tile_id, it's\n * a no-op (the same subscription handles all strips on the page \u2014\n * filter is just per-event check).\n */\n private _subscribeCompositional(): void {\n // Call via the bus object so `this` stays bound to the EventBus\n // instance \u2014 extracting the method to a local would lose its\n // `this.subscriptions` and throw at the first call.\n const bus = this.runtimeRef?.events;\n if (!bus?.subscribe || !this._tileId) return;\n const tileId = this._tileId;\n this._compositionalUnsub = bus.subscribe((event) => {\n const props = event.props ?? {};\n // Filter to events targeting THIS strip's tile.\n if (props.tile_id !== tileId) return;\n if (event.name === 'element.compositional_append') {\n this.insertItem(\n String(props.instance_id ?? ''),\n props.item as Record<string, unknown>,\n (props.position as 'append' | 'prepend') ?? 'append'\n );\n } else if (event.name === 'element.compositional_patch') {\n this.patchItem(String(props.instance_id ?? ''), props.item as Record<string, unknown>);\n } else if (event.name === 'element.compositional_remove') {\n this.removeItem(String(props.instance_id ?? ''));\n }\n });\n // Ask the store to replay any items it already has for our tile.\n // The store is a module-singleton that survives chat-bar unmount/\n // remount, so SPA-navigating away and back leaves persisted items\n // in memory; the replay re-publishes append events the subscription\n // above picks up. Dedup on `insertItem` keeps it idempotent if a\n // fresh hydrate is also in flight.\n bus.publish?.('element.compositional_replay_request', { tile_id: tileId });\n }\n\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // CompositionalContainer contract \u2014 `ItemHandler` translates wire\n // mutations into events that call these methods. The wire `item`\n // shape is fully formed (`{kind, config: {id, title, payload, \u2026}}`);\n // the strip just appends, patches, or removes by `instance_id`.\n // `instance_id` is the canonical id; chip.config.id is set to it by\n // the backend, so they're the same value. We store the id on\n // `chip.config.id` and use that as the lookup key.\n // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** Append (or prepend) a new chip authored by the LLM. */\n insertItem(\n instanceId: string,\n item: Record<string, unknown>,\n position: 'append' | 'prepend'\n ): void {\n if (!instanceId) return;\n if (this._llmAppendedIds.has(instanceId)) return; // dedup\n // The wire shape is `{kind: 'suggestions:chip', config: {\u2026}}`. We\n // accept it as-is \u2014 fully-formed chip envelope from the backend.\n const chip = item as unknown as SuggestionChip;\n if (!chip?.config?.id) return;\n this._llmAppendedIds.add(instanceId);\n this.chips = position === 'prepend' ? [chip, ...this.chips] : [...this.chips, chip];\n }\n\n /** Replace the existing chip's content. v1: full replacement (no\n * in-place merge); the chip widget already re-renders on chip data\n * changes. */\n patchItem(instanceId: string, item: Record<string, unknown>): void {\n if (!instanceId) return;\n const idx = this.chips.findIndex((c) => c.config.id === instanceId);\n if (idx < 0) return;\n const next = [...this.chips];\n next[idx] = item as unknown as SuggestionChip;\n this.chips = next;\n }\n\n /** Remove a chip by instance id. Closes the payload drawer if the\n * removed chip was the open one. */\n removeItem(instanceId: string): void {\n if (!instanceId) return;\n const next = this.chips.filter((c) => c.config.id !== instanceId);\n if (next.length === this.chips.length) return;\n this.chips = next;\n this._llmAppendedIds.delete(instanceId);\n if (this._openId === instanceId) this._openId = null;\n }\n\n /**\n * Attach a ResizeObserver to the scroll container so we recompute\n * left/right arrow visibility when chips are added/removed or the\n * panel width changes. The scroll listener on the strip element\n * handles user-driven scrolls.\n */\n private _syncStripObserver(): void {\n const strip = this.querySelector<HTMLElement>('[data-chips-strip]');\n if (!strip) {\n this._stripResizeObserver?.disconnect();\n this._stripResizeObserver = null;\n return;\n }\n // JSDOM (component tests) doesn't ship ResizeObserver. The strip\n // still functions without it \u2014 _updateScrollAffordances runs on\n // every `updated()` pass and on scroll events, so the only thing\n // missing is the \"external width change while no chip data changed\"\n // sync, which can't be exercised in a JSDOM unit test anyway.\n if (typeof ResizeObserver === 'undefined') return;\n if (!this._stripResizeObserver) {\n this._stripResizeObserver = new ResizeObserver(() => this._updateScrollAffordances());\n }\n // disconnect+observe is cheap and avoids dangling observation when\n // Lit recycles the DOM node for whatever reason.\n this._stripResizeObserver.disconnect();\n this._stripResizeObserver.observe(strip);\n }\n\n /**\n * Recompute whether the left/right scroll arrows should be visible.\n * Left arrow \u21D0 scrollLeft > 0. Right arrow \u21D0 there are more chips\n * to the right (scrollLeft + clientWidth < scrollWidth). Threshold\n * of 1px tolerance handles sub-pixel rounding on scaled displays.\n */\n private _updateScrollAffordances(): void {\n const strip = this.querySelector<HTMLElement>('[data-chips-strip]');\n if (!strip) return;\n const canLeft = strip.scrollLeft > 1;\n const canRight = strip.scrollLeft + strip.clientWidth < strip.scrollWidth - 1;\n if (canLeft !== this._canScrollLeft) this._canScrollLeft = canLeft;\n if (canRight !== this._canScrollRight) this._canScrollRight = canRight;\n }\n\n private _onStripScroll = (): void => {\n this._updateScrollAffordances();\n };\n\n /**\n * Scroll the strip by ~80% of its visible width, so each arrow click\n * advances the user past most-but-not-all of the current chips\n * (a small overlap helps the user keep their place in the row).\n */\n private _scrollBy(direction: 'left' | 'right'): void {\n const strip = this.querySelector<HTMLElement>('[data-chips-strip]');\n if (!strip) return;\n const delta = strip.clientWidth * 0.8 * (direction === 'left' ? -1 : 1);\n strip.scrollBy({ left: delta, behavior: 'smooth' });\n }\n\n private _unmountPayload(): void {\n if (this._payloadMount) {\n try {\n this._payloadMount.handle.unmount();\n } catch (err) {\n console.warn('[adaptive-chips-strip] payload widget unmount threw:', err);\n }\n this._payloadMount = null;\n }\n }\n\n /**\n * Reconcile the payload mount with the current `_openId`:\n * - drawer closed / no runtime / widget id unknown \u2192 tear down\n * - same chip + same widget id \u2192 no-op (or props update)\n * - different chip \u2192 unmount old, mount new\n */\n private _syncPayloadMount(): void {\n const chip = this.chips.find((c) => c.config.id === this._openId) ?? null;\n const widgets = this.runtimeRef?.widgets;\n if (!chip || !widgets || !widgets.has(chip.config.payload.widget)) {\n this._unmountPayload();\n return;\n }\n const nextWidget = chip.config.payload.widget;\n const nextProps = chip.config.payload.props ?? {};\n if (\n this._payloadMount?.chipId === chip.config.id &&\n this._payloadMount.widgetId === nextWidget\n ) {\n // Same chip + same widget \u2014 update props in-place if the widget\n // supports it; otherwise leave untouched (no-op covers the\n // common no-change render case).\n this._payloadMount.handle.update?.(nextProps);\n return;\n }\n this._unmountPayload();\n const container = this.querySelector<HTMLElement>('[data-chip-payload-mount]');\n if (!container) {\n // Template hasn't laid down the mount target yet (Lit microtask\n // race). Retry after paint \u2014 without this, fast chip clicks can\n // land on a render where _openId changes but the conditional\n // template branch hasn't committed, and the payload silently\n // never mounts.\n requestAnimationFrame(() => {\n if (this._openId !== chip.config.id) return;\n const retry = this.querySelector<HTMLElement>('[data-chip-payload-mount]');\n if (!retry) return;\n const handle = widgets.mount(nextWidget, retry, nextProps);\n this._payloadMount = { chipId: chip.config.id, widgetId: nextWidget, handle };\n });\n return;\n }\n const handle = widgets.mount(nextWidget, container, nextProps);\n this._payloadMount = { chipId: chip.config.id, widgetId: nextWidget, handle };\n }\n\n private _onChipClick(chip: SuggestionChip): void {\n if (this._openId === chip.config.id) {\n // Toggle closed.\n this._openId = null;\n return;\n }\n this._openId = chip.config.id;\n this.dispatchEvent(\n new CustomEvent<{ id: string; payload: ChipPayload }>('chip-revealed', {\n detail: { id: chip.config.id, payload: chip.config.payload },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private _onChipDismiss(chip: SuggestionChip, e: Event): void {\n // Don't let the dismiss click bubble up to the chip's own click\n // handler \u2014 the user is removing the chip, not opening it.\n e.stopPropagation();\n // If the dismissed chip was the open one, close the drawer first\n // so the payload region doesn't briefly point at nothing.\n if (this._openId === chip.config.id) this._openId = null;\n this.dispatchEvent(\n new CustomEvent<{ id: string }>('chip-dismissed', {\n detail: { id: chip.config.id },\n bubbles: true,\n composed: true,\n })\n );\n }\n\n private _onPayloadClose = (): void => {\n this._openId = null;\n };\n\n override render(): TemplateResult {\n const open = this.chips.find((c) => c.config.id === this._openId) ?? null;\n\n return html`\n <div data-syntro-chips-tile style=${styleMap(rootStyles())}>\n <div data-chips-strip-frame style=${styleMap(stripFrameStyles())}>\n ${\n this._canScrollLeft\n ? html`<button\n type=\"button\"\n data-chips-scroll=\"left\"\n aria-label=\"Scroll suggestions left\"\n @click=${() => this._scrollBy('left')}\n style=${styleMap(scrollButtonStyles('left'))}\n >\u2039</button>`\n : nothing\n }\n <div\n data-chips-strip\n style=${styleMap(stripStyles())}\n @scroll=${this._onStripScroll}\n >\n ${this.chips.map(\n (chip) => html`<button\n type=\"button\"\n data-chip-id=${chip.config.id}\n data-syntro-element-id=${chip.config.id}\n data-state=${this._openId === chip.config.id ? 'open' : 'closed'}\n data-chromeless=${this.chromeless ? 'true' : 'false'}\n style=${styleMap(chipStyles(this._openId === chip.config.id, this.chromeless))}\n @click=${() => this._onChipClick(chip)}\n >\n <span style=${styleMap(chipTitleStyles())}>${chip.config.title}</span>\n <span style=${styleMap(chipGlyphStyles())}>\n ${this._openId === chip.config.id ? '\u25BC' : '\u203A'}\n </span>\n ${\n // Per-chip dismiss \u00D7 is suppressed when the strip is\n // chromeless \u2014 LLM-curated suggestions in a mini-\n // canvas surface shouldn't be user-dismissible (the\n // model picks them; the user can't \"remove\" them\n // any more than they can remove a paragraph of\n // prose). Also suppressed on the open chip in any\n // mode (the chip's button-toggle handles close).\n this.chromeless || this._openId === chip.config.id\n ? nothing\n : html`<span\n role=\"button\"\n tabindex=\"0\"\n data-chip-dismiss=${chip.config.id}\n aria-label=\"Dismiss suggestion\"\n title=\"Dismiss\"\n style=${styleMap(chipDismissStyles())}\n @click=${(e: Event) => this._onChipDismiss(chip, e)}\n @keydown=${(e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this._onChipDismiss(chip, e);\n }\n }}\n >\u00D7</span>`\n }\n </button>`\n )}\n </div>\n ${\n this._canScrollRight\n ? html`<button\n type=\"button\"\n data-chips-scroll=\"right\"\n aria-label=\"Scroll suggestions right\"\n @click=${() => this._scrollBy('right')}\n style=${styleMap(scrollButtonStyles('right'))}\n >\u203A</button>`\n : nothing\n }\n </div>\n ${\n open\n ? html`<div\n data-chip-payload\n data-for-chip=${open.config.id}\n data-chromeless=${this.chromeless ? 'true' : 'false'}\n style=${styleMap(payloadStyles(this.chromeless))}\n >\n <!--\n Drawer header \u2014 title (the Q in Q&A) ALWAYS renders\n because it's the actual content of the answer\n pairing, not chrome. Chromeless mode drops the\n surrounding chrome (border, background, \u2303 close)\n but the title stays so the chip \u2192 answer link is\n visible above the body. User closes by re-clicking\n the chip.\n -->\n <div\n data-chip-payload-header\n style=${styleMap(payloadHeaderStyles(this.chromeless))}\n >\n <span>${open.config.title}</span>\n ${\n this.chromeless\n ? nothing\n : html`<button\n type=\"button\"\n data-chip-payload-close\n aria-label=\"Collapse\"\n @click=${this._onPayloadClose}\n style=${styleMap(payloadCloseStyles())}\n >\u2303</button>`\n }\n </div>\n <div style=${styleMap(payloadBodyStyles(this.chromeless))}>\n <!--\n Payload widget mounts here imperatively in updated().\n The mount container is a stable element across renders so\n the runtime registry can track + update + unmount\n properly. If the widget id is unknown to the registry,\n we leave a labeled placeholder so the gap is visible\n in development rather than silently empty.\n -->\n ${\n this.runtimeRef?.widgets?.has(open.config.payload.widget)\n ? html`<div data-chip-payload-mount></div>`\n : html`<p style=${styleMap(unresolvedStyles())}>\n Widget <code>${open.config.payload.widget}</code> not registered.\n </p>`\n }\n </div>\n </div>`\n : nothing\n }\n </div>\n `;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Styles\n// ---------------------------------------------------------------------------\n\nfunction rootStyles(): Record<string, string> {\n return {\n display: 'flex',\n flexDirection: 'column',\n gap: '10px',\n width: '100%',\n };\n}\n\nfunction stripFrameStyles(): Record<string, string> {\n return {\n position: 'relative',\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n };\n}\n\nfunction stripStyles(): Record<string, string> {\n return {\n display: 'flex',\n flexDirection: 'row',\n // Single row \u2014 chips never wrap. Horizontal overflow becomes\n // a scrollable region with left/right arrow affordances on either\n // side of the frame. `flex-wrap: wrap` (the old behavior) packed\n // chips into multiple rows, which broke the mini-canvas surface's\n // visual rhythm \u2014 chat-bar above, ONE row of chips, payload below.\n flexWrap: 'nowrap',\n gap: '8px',\n width: '100%',\n overflowX: 'auto',\n overflowY: 'hidden',\n // Hide the native scrollbar \u2014 the chevron buttons are the\n // affordance. Firefox: `scrollbarWidth: 'none'`. WebKit: the\n // global `::-webkit-scrollbar { display: none }` rule in\n // chatTrailGlobalStyles.ts (which is shared chat-surface CSS) is\n // what hides it across all our scroll surfaces.\n scrollbarWidth: 'none',\n scrollSnapType: 'x proximity',\n scrollBehavior: 'smooth',\n // Don't let touch scroll bubble up to the page when the strip\n // can scroll horizontally \u2014 keeps wheel scroll on the canvas\n // intact while letting the strip move under the cursor.\n overscrollBehavior: 'contain',\n };\n}\n\n/**\n * Left/right scroll arrows. Absolute-positioned on either edge of the\n * frame so they overlay the chips at the edge \u2014 combined with the\n * frame's relative positioning, they sit on top of the chip pills at\n * the boundary, with a small backdrop blur so the underlying chip is\n * still legible. Visible only when there's more content in that\n * direction (controlled by `_canScrollLeft` / `_canScrollRight`).\n */\nfunction scrollButtonStyles(side: 'left' | 'right'): Record<string, string> {\n return {\n position: 'absolute',\n [side]: '0',\n top: '50%',\n transform: 'translateY(-50%)',\n zIndex: '2',\n width: '24px',\n height: '24px',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '0',\n borderRadius: '9999px',\n border: '1px solid hsl(var(--sc-tile-border-color, 0 0% 100%) / 0.18)',\n background: 'hsl(var(--sc-accent-color) / 0.18)',\n backdropFilter: 'blur(6px)',\n WebkitBackdropFilter: 'blur(6px)',\n color: 'var(--sc-tile-title-color, currentColor)',\n fontSize: '14px',\n lineHeight: '1',\n cursor: 'pointer',\n opacity: '0.85',\n transition: 'opacity 150ms ease, background 150ms ease',\n };\n}\n\nfunction chipStyles(isOpen: boolean, _chromeless: boolean): Record<string, string> {\n // The chip itself ALWAYS renders as a pill with title. Chromeless\n // is about dropping wrapping chrome (tile-card, drawer chrome,\n // per-chip dismiss \u00D7), not about turning the chip into raw text \u2014\n // mixed-style chips inside a single strip looked bad and lost the\n // affordance. Keep the pill, drop the surroundings.\n return {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n // Flex to text width up to a sane cap. The title span inside owns\n // the ellipsis safety net for genuinely-long titles.\n maxWidth: '32ch',\n color: isOpen\n ? 'var(--sc-tile-title-color, currentColor)'\n : 'var(--sc-tile-text-color, currentColor)',\n fontSize: '12px',\n fontWeight: '600',\n cursor: 'pointer',\n transition: 'background 180ms ease, border-color 180ms ease, color 180ms ease',\n padding: '6px 12px',\n borderRadius: '9999px',\n border: isOpen\n ? '1px solid hsl(var(--sc-accent-color) / 0.45)'\n : '1px solid hsl(var(--sc-tile-border-color, 0 0% 100%) / 0.18)',\n background: isOpen ? 'hsl(var(--sc-accent-color) / 0.10)' : 'transparent',\n };\n}\n\nfunction chipTitleStyles(): Record<string, string> {\n return {\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: '0',\n };\n}\n\n// Small \u2715 inside each closed chip. Uses a span+role=\"button\" so it\n// nests inside the chip's <button> without violating \"no interactive\n// inside interactive\" \u2014 the click handler stopPropagation-s so it\n// doesn't open the chip.\nfunction chipDismissStyles(): Record<string, string> {\n return {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: '16px',\n height: '16px',\n marginLeft: '2px',\n borderRadius: '9999px',\n color: 'inherit',\n fontSize: '12px',\n lineHeight: '1',\n cursor: 'pointer',\n opacity: '0.45',\n flexShrink: '0',\n transition: 'opacity 150ms ease, background 150ms ease',\n };\n}\n\nfunction chipGlyphStyles(): Record<string, string> {\n return {\n fontSize: '10px',\n opacity: '0.7',\n flexShrink: '0',\n };\n}\n\nfunction payloadStyles(chromeless: boolean): Record<string, string> {\n const base: Record<string, string> = {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n color: 'var(--sc-tile-title-color, currentColor)',\n };\n if (chromeless) {\n // No drawer chrome \u2014 the payload sits flush with the strip\n // above it. Visual separation comes from typography (the chip's\n // underline) and slight spacing, not borders/backgrounds.\n return {\n ...base,\n padding: '4px 0 0',\n borderRadius: '0',\n border: 'none',\n background: 'transparent',\n };\n }\n return {\n ...base,\n padding: '10px 12px',\n borderRadius: 'var(--sc-tile-border-radius, 12px)',\n border: '1px solid hsl(var(--sc-accent-color) / 0.18)',\n background: 'hsl(var(--sc-accent-color) / 0.04)',\n };\n}\n\nfunction payloadHeaderStyles(chromeless: boolean): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: '8px',\n fontSize: '12px',\n fontWeight: '600',\n // In chromeless mode the header is the only visible heading for\n // the answer below (no surrounding box/border). Use the accent\n // color so the Q reads distinctly as the prompt for the A.\n ...(chromeless ? { color: 'hsl(var(--sc-accent-color) / 0.95)' } : {}),\n };\n}\n\nfunction payloadCloseStyles(): Record<string, string> {\n return {\n width: '22px',\n height: '22px',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: '9999px',\n background: 'transparent',\n border: '1px solid hsl(var(--sc-tile-border-color, 0 0% 100%) / 0.18)',\n color: 'inherit',\n fontSize: '11px',\n cursor: 'pointer',\n opacity: '0.65',\n };\n}\n\nfunction payloadBodyStyles(chromeless: boolean): Record<string, string> {\n return {\n fontSize: '12px',\n lineHeight: '1.55',\n color: 'var(--sc-tile-text-color, currentColor)',\n // No top divider needed when the header is suppressed.\n ...(chromeless ? {} : {}),\n };\n}\n\nfunction unresolvedStyles(): Record<string, string> {\n return {\n margin: '0',\n fontStyle: 'italic',\n opacity: '0.7',\n };\n}\n\nif (!customElements.get('adaptive-chips-strip')) {\n customElements.define('adaptive-chips-strip', AdaptiveChipsStrip);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'adaptive-chips-strip': AdaptiveChipsStrip;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["handle"]
7
+ }
@@ -0,0 +1,382 @@
1
+ import {
2
+ renderMarkdown
3
+ } from "./chunk-UC4XU6GH.js";
4
+
5
+ // src/AdaptiveChatTrail.ts
6
+ import { html, LitElement, nothing } from "lit";
7
+ import { styleMap } from "lit/directives/style-map.js";
8
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
9
+ var DEFAULT_VISIBLE = 3;
10
+ var OPACITY_STEP = 0.22;
11
+ var OPACITY_FLOOR = 0.18;
12
+ var Y_DRIFT_PX = -2;
13
+ var TRAIL_BLUR_VEIL_PX = 72;
14
+ var TRAIL_BLUR_MAX_PX = 5;
15
+ var AdaptiveChatTrail = class extends LitElement {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.messages = [];
19
+ this.visibleCount = DEFAULT_VISIBLE;
20
+ this.expanded = false;
21
+ /**
22
+ * Pre-conversation phantom assistant message. Rendered only when
23
+ * `messages` is empty. NOT injected into chatSession state — the
24
+ * greeting is configuration, not conversation, so it disappears
25
+ * automatically when the first real message arrives.
26
+ */
27
+ this.greeting = void 0;
28
+ this._onExpand = () => {
29
+ this.expanded = true;
30
+ this.dispatchEvent(new CustomEvent("trail-expand", { bubbles: true, composed: true }));
31
+ };
32
+ /**
33
+ * Approve a pending client tool call. The trail emits a generic
34
+ * event so the host can decide whether to forward to chatSession
35
+ * (the common path) or override. Keeps the trail itself free of
36
+ * direct chatSession coupling — it's a pure view component.
37
+ */
38
+ this._onToolCallClick = (tc) => {
39
+ if (tc.status !== "pending") return;
40
+ this.dispatchEvent(
41
+ new CustomEvent("trail-toolcall-approved", {
42
+ detail: { toolCallId: tc.id, approved: true },
43
+ bubbles: true,
44
+ composed: true
45
+ })
46
+ );
47
+ };
48
+ this._onCollapse = () => {
49
+ this.expanded = false;
50
+ this.dispatchEvent(new CustomEvent("trail-collapse", { bubbles: true, composed: true }));
51
+ };
52
+ }
53
+ createRenderRoot() {
54
+ return this;
55
+ }
56
+ connectedCallback() {
57
+ super.connectedCallback();
58
+ ensureStreamingKeyframes();
59
+ }
60
+ updated(changed) {
61
+ if (changed.has("expanded") || changed.has("messages")) {
62
+ requestAnimationFrame(() => {
63
+ const container = this.querySelector("[data-syntro-chat-trail]");
64
+ if (!container) return;
65
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
66
+ if (distanceFromBottom < 200 || changed.has("expanded")) {
67
+ container.scrollTop = container.scrollHeight;
68
+ }
69
+ });
70
+ }
71
+ }
72
+ render() {
73
+ if (this.messages.length === 0 && !this.greeting) return nothing;
74
+ if (this.messages.length === 0 && this.greeting) {
75
+ return html`<div data-syntro-chat-trail style=${styleMap({
76
+ display: "flex",
77
+ flexDirection: "column",
78
+ gap: "6px",
79
+ justifyContent: "flex-end",
80
+ width: "100%",
81
+ padding: "0",
82
+ pointerEvents: "auto"
83
+ })}>
84
+ <div
85
+ data-trail-chip
86
+ data-role="assistant"
87
+ data-status="greeting"
88
+ style=${styleMap(chipStyles("assistant", 0, { isStreaming: false, isError: false }))}
89
+ >${unsafeHTML(renderMarkdown(stripTrailingWhitespace(this.greeting)))}</div>
90
+ </div>`;
91
+ }
92
+ const visible = this.expanded ? this.messages : this.messages.slice(-this.visibleCount);
93
+ const hidden = this.messages.length - visible.length;
94
+ const baseStyles = {
95
+ display: "flex",
96
+ flexDirection: "column",
97
+ gap: "6px",
98
+ // NO `justifyContent: flex-end` here — combined with `overflow: auto`
99
+ // it's a known cross-browser bug: oversized flex children get
100
+ // pushed ABOVE the container without contributing to scrollHeight,
101
+ // so the scrollbar never appears. We rely on `:first-child {
102
+ // margin-top: auto }` (injected as global CSS) to push short
103
+ // content to the bottom of the box while leaving overflow handling
104
+ // to the native scroll container.
105
+ width: "100%",
106
+ // The trail itself has no chrome — chips sit on the host page bg.
107
+ padding: "0",
108
+ pointerEvents: "auto"
109
+ };
110
+ const modeStyles = this.expanded ? {
111
+ maxHeight: "min(320px, 40vh)",
112
+ overflowY: "auto",
113
+ paddingTop: "8px",
114
+ borderTop: "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))",
115
+ scrollBehavior: "smooth",
116
+ scrollbarWidth: "thin"
117
+ } : {
118
+ // Collapsed mode = ambient peek surface. Never a scrollbar:
119
+ // long messages slide up behind the blur veil instead of
120
+ // exposing a scrollbar. The "↑ N more · expand" affordance
121
+ // is the only entry into history; clicking it switches to
122
+ // expanded mode where scroll IS allowed.
123
+ // Height kept short so the trail reads as a peek strip above
124
+ // the chat bar, not a panel — long single replies fade into
125
+ // the blur veil at the top, user expands to read the rest.
126
+ maxHeight: "min(140px, 22vh)",
127
+ overflow: "hidden"
128
+ };
129
+ const containerStyles = { ...baseStyles, ...modeStyles };
130
+ const frameStyles = {
131
+ position: "relative",
132
+ width: "100%"
133
+ };
134
+ return html`
135
+ ${// Single toggle button above the frame. Same DOM node morphs:
136
+ // collapsed → "↑ N more · expand" / "↑ expand" (calls _onExpand)
137
+ // expanded → "⌄ minimize" (calls _onCollapse)
138
+ // Lives OUTSIDE the frame so the absolute blur veil (top:0,
139
+ // z-index:2 inside the frame) doesn't cover it. Shown whenever
140
+ // there's anything in the trail — the user expects this affordance
141
+ // available regardless of how many chips are on screen.
142
+ this.messages.length === 0 ? nothing : this.expanded ? html`<button
143
+ type="button"
144
+ data-trail-toggle
145
+ data-trail-collapse
146
+ @click=${this._onCollapse}
147
+ style=${styleMap(moreStyles())}
148
+ >⌄ minimize</button>` : html`<button
149
+ type="button"
150
+ data-trail-toggle
151
+ data-trail-more
152
+ @click=${this._onExpand}
153
+ style=${styleMap(moreStyles())}
154
+ >${hidden > 0 ? html`↑ ${hidden} more · expand` : html`↑ expand`}</button>`}
155
+ <div data-syntro-chat-trail-frame style=${styleMap(frameStyles)}>
156
+ <div data-syntro-chat-trail style=${styleMap(containerStyles)}>
157
+ ${visible.map((m, i) => {
158
+ const pos = this.expanded ? 0 : visible.length - 1 - i;
159
+ const isStreaming = m.status === "streaming";
160
+ const isError = m.status === "error" || m.role === "system";
161
+ const toolCalls = m.toolCalls ?? [];
162
+ const renderedText = m.role === "assistant" ? html`${unsafeHTML(renderMarkdown(stripTrailingWhitespace(m.text)))}` : html`${m.text}`;
163
+ return html`<div
164
+ data-trail-chip
165
+ data-role=${m.role}
166
+ data-status=${m.status ?? "complete"}
167
+ style=${styleMap(chipStyles(m.role, pos, { isStreaming, isError }))}
168
+ >${renderedText}${isStreaming ? html`<span
169
+ data-trail-caret
170
+ aria-hidden="true"
171
+ style=${styleMap(caretStyles())}
172
+ ></span>` : nothing}${toolCalls.length > 0 ? html`<div data-trail-toolcalls style=${styleMap(toolCallStripStyles())}>
173
+ ${toolCalls.map(
174
+ (tc) => html`<button
175
+ type="button"
176
+ data-trail-toolcall
177
+ data-tool-id=${tc.id}
178
+ data-tool-status=${tc.status}
179
+ @click=${() => this._onToolCallClick(tc)}
180
+ ?disabled=${tc.status !== "pending"}
181
+ style=${styleMap(toolCallChipStyles(tc.status))}
182
+ title="${tc.name} · ${tc.status}"
183
+ >${toolCallIcon(tc.status)} ${tc.name}</button>`
184
+ )}
185
+ </div>` : nothing}</div>`;
186
+ })}
187
+ </div>
188
+ ${// The blur veil only fires in collapsed (ambient) mode.
189
+ // Expanded = the user explicitly asked for history; blurring
190
+ // the top of the panel would hide content (including the
191
+ // minimize button just above the trail's top edge) the user
192
+ // came here to see.
193
+ this.expanded ? nothing : html`<div
194
+ data-trail-blur-veil
195
+ aria-hidden="true"
196
+ style=${styleMap(blurVeilStyles())}
197
+ ></div>`}
198
+ </div>
199
+ `;
200
+ }
201
+ };
202
+ AdaptiveChatTrail.properties = {
203
+ messages: { attribute: false },
204
+ visibleCount: { type: Number },
205
+ expanded: { type: Boolean },
206
+ greeting: { type: String }
207
+ };
208
+ function blurVeilStyles() {
209
+ const mask = "linear-gradient(to bottom, black 0%, black 40%, transparent 100%)";
210
+ return {
211
+ position: "absolute",
212
+ top: "0",
213
+ left: "0",
214
+ right: "0",
215
+ height: `${TRAIL_BLUR_VEIL_PX}px`,
216
+ pointerEvents: "none",
217
+ backdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,
218
+ WebkitBackdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,
219
+ maskImage: mask,
220
+ WebkitMaskImage: mask,
221
+ zIndex: "2"
222
+ };
223
+ }
224
+ function chipStyles(role, pos, state = { isStreaming: false, isError: false }) {
225
+ const opacity = Math.max(OPACITY_FLOOR, 1 - pos * OPACITY_STEP);
226
+ const yPx = pos * Y_DRIFT_PX;
227
+ const border = state.isError ? "var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.55))" : state.isStreaming ? "var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))" : role === "user" ? "var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))" : "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))";
228
+ const background = state.isError ? "var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.42))" : role === "user" ? "var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))" : "var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))";
229
+ const base = {
230
+ alignSelf: role === "user" ? "flex-end" : "flex-start",
231
+ maxWidth: "85%",
232
+ fontSize: "11px",
233
+ lineHeight: "1.45",
234
+ padding: "4px 10px",
235
+ borderRadius: "10px",
236
+ border,
237
+ background,
238
+ backdropFilter: "blur(8px)",
239
+ WebkitBackdropFilter: "blur(8px)",
240
+ color: state.isError ? "var(--sc-content-bubble-text-error, rgba(255, 220, 220, 0.95))" : "var(--sc-tile-title-color, inherit)",
241
+ transition: "opacity 240ms cubic-bezier(0.22, 1, 0.36, 1), transform 240ms cubic-bezier(0.22, 1, 0.36, 1)",
242
+ // Per-position opacity + y-drift falloff is still per-chip — those
243
+ // operate naturally per element. Blur is NOT per-chip anymore: it's
244
+ // applied via an absolute-positioned veil at the top of the scroll
245
+ // viewport (TRAIL_BLUR_VEIL_PX above), so a single long message
246
+ // doesn't get uniformly blurred across its full body.
247
+ opacity: String(toFixedTrim(opacity)),
248
+ transform: `translateY(${yPx}px)`,
249
+ whiteSpace: "pre-wrap",
250
+ wordBreak: "break-word"
251
+ };
252
+ return base;
253
+ }
254
+ function toolCallStripStyles() {
255
+ return {
256
+ display: "flex",
257
+ flexWrap: "wrap",
258
+ gap: "4px",
259
+ marginTop: "4px",
260
+ paddingTop: "4px",
261
+ borderTop: "1px dashed var(--sc-content-divider-color, rgba(255, 255, 255, 0.12))"
262
+ };
263
+ }
264
+ function toolCallChipStyles(status) {
265
+ const isPending = status === "pending";
266
+ const isError = status === "error";
267
+ const isDone = status === "done";
268
+ return {
269
+ display: "inline-flex",
270
+ alignItems: "center",
271
+ gap: "4px",
272
+ padding: "2px 7px",
273
+ border: isError ? "var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.45))" : isPending ? "var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))" : "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))",
274
+ background: isError ? "var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.35))" : isPending ? "var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))" : isDone ? "var(--sc-content-bubble-background-idle, rgba(40, 44, 50, 0.4))" : "var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))",
275
+ color: "var(--sc-tile-text-color, inherit)",
276
+ borderRadius: "999px",
277
+ fontSize: "10px",
278
+ fontWeight: "500",
279
+ fontFamily: "ui-monospace, SF Mono, Menlo, monospace",
280
+ cursor: isPending ? "pointer" : "default",
281
+ opacity: isDone ? "0.7" : "1"
282
+ };
283
+ }
284
+ function toolCallIcon(status) {
285
+ switch (status) {
286
+ case "args-streaming":
287
+ case "running":
288
+ return "\u22EF";
289
+ case "pending":
290
+ return "?";
291
+ case "done":
292
+ return "\u2713";
293
+ case "error":
294
+ return "\u2717";
295
+ default:
296
+ return "\xB7";
297
+ }
298
+ }
299
+ function caretStyles() {
300
+ return {
301
+ display: "inline-block",
302
+ width: "6px",
303
+ height: "12px",
304
+ marginLeft: "3px",
305
+ verticalAlign: "-1px",
306
+ background: "hsl(var(--sc-accent-color) / 0.85)",
307
+ borderRadius: "1px",
308
+ animation: "syntro-trail-caret 1s steps(2, end) infinite"
309
+ };
310
+ }
311
+ function moreStyles() {
312
+ return {
313
+ // Center horizontally regardless of the parent's display model.
314
+ // ``alignSelf: center`` only works when the parent is a flex/grid
315
+ // container with cross-axis alignment; the chat-bar's flex column
316
+ // stretches us full-width instead. ``display: block`` + auto inline
317
+ // margins centers the auto-sized button without needing the parent
318
+ // to opt in.
319
+ display: "block",
320
+ marginLeft: "auto",
321
+ marginRight: "auto",
322
+ marginBottom: "6px",
323
+ fontSize: "10px",
324
+ fontWeight: "500",
325
+ letterSpacing: "0.06em",
326
+ padding: "4px 10px",
327
+ border: "1px solid hsl(var(--sc-accent-color) / 0.32)",
328
+ borderRadius: "999px",
329
+ background: "hsl(var(--sc-accent-color) / 0.10)",
330
+ color: "var(--sc-tile-text-color, currentColor)",
331
+ cursor: "pointer",
332
+ opacity: "0.85",
333
+ backdropFilter: "blur(8px)",
334
+ WebkitBackdropFilter: "blur(8px)",
335
+ transition: "opacity 150ms ease, background 150ms ease"
336
+ };
337
+ }
338
+ function ensureStreamingKeyframes() {
339
+ if (typeof document === "undefined") return;
340
+ if (document.head.querySelector("style[data-syntro-trail-caret-keyframes]")) return;
341
+ const style = document.createElement("style");
342
+ style.setAttribute("data-syntro-trail-caret-keyframes", "true");
343
+ style.textContent = [
344
+ "@keyframes syntro-trail-caret { 0%{opacity:1} 50%{opacity:0} 100%{opacity:1} }",
345
+ // Anchor short content to the bottom of the scrollable trail —
346
+ // see baseStyles comment for why we can't use justify-content.
347
+ "[data-syntro-chat-trail] > :first-child { margin-top: auto }",
348
+ // Tight spacing — trail chips are an 11px-line-height pill. Default
349
+ // browser <p>/<ul> margins (1em ≈ 11px each side) would dominate the
350
+ // chip. Keep paragraph + list separators minimal (4px) so multi-
351
+ // paragraph replies read as one continuous flow, not a vertical
352
+ // stack of fragments.
353
+ "[data-trail-chip] > p:first-child { margin-top: 0 }",
354
+ "[data-trail-chip] > p:last-child { margin-bottom: 0 }",
355
+ "[data-trail-chip] p { margin: 4px 0 }",
356
+ "[data-trail-chip] br + br { display: none }",
357
+ "[data-trail-chip] ul, [data-trail-chip] ol { margin: 4px 0; padding-left: 1.1em }",
358
+ "[data-trail-chip] li { margin: 0 }",
359
+ "[data-trail-chip] li + li { margin-top: 1px }",
360
+ "[data-trail-chip] pre { margin: 6px 0; padding: 6px 8px; background: var(--sc-content-code-background-block, rgba(0, 0, 0, 0.35)); border-radius: 6px; overflow-x: auto; font-size: 10px }",
361
+ "[data-trail-chip] code { font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: 10px; background: var(--sc-content-code-background, rgba(0, 0, 0, 0.25)); padding: 1px 4px; border-radius: 3px }",
362
+ "[data-trail-chip] pre code { background: transparent; padding: 0 }",
363
+ "[data-trail-chip] a { color: var(--sc-content-link-color, var(--sc-color-primary, #b72e2a)); text-decoration: underline }",
364
+ "[data-trail-chip] strong { font-weight: 600 }"
365
+ ].join(" ");
366
+ document.head.appendChild(style);
367
+ }
368
+ function stripTrailingWhitespace(text) {
369
+ return text.replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
370
+ }
371
+ function toFixedTrim(n) {
372
+ const s = n.toFixed(2);
373
+ return s.replace(/\.?0+$/, "") || "0";
374
+ }
375
+ if (!customElements.get("adaptive-chat-trail")) {
376
+ customElements.define("adaptive-chat-trail", AdaptiveChatTrail);
377
+ }
378
+
379
+ export {
380
+ AdaptiveChatTrail
381
+ };
382
+ //# sourceMappingURL=chunk-DOMEUJR7.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/AdaptiveChatTrail.ts"],
4
+ "sourcesContent": ["/**\n * AdaptiveChatTrail \u2014 the \"bubble-up\" message column that sits above the\n * chat bar in the canvas lid.\n *\n * Messages appear immediately above the chat bar and drift upward as\n * newer ones arrive. Each stack position carries an opacity + blur\n * falloff so older messages read as fading from the page. Beyond a\n * `visibleCount` cap (default 3) the trail collapses into an \"N more \u00B7\n * expand\" affordance \u2014 clicking emits `trail-expand`.\n *\n * Light DOM (no shadow root) so the host page's CSS variables and the\n * surrounding canvas tokens flow through without a nested shadow\n * boundary.\n *\n * See PRD \u00A74.3 (chat trail) for the canonical motion + falloff spec.\n */\n\nimport { renderMarkdown } from '@syntrologie/chat';\nimport { html, LitElement, nothing } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\n\nexport interface TrailToolCall {\n id: string;\n name: string;\n status: 'args-streaming' | 'pending' | 'running' | 'done' | 'error';\n}\n\nexport interface TrailMessage {\n /** Stable identity for keyed rendering (must be unique within the trail). */\n id: number | string;\n role: 'user' | 'assistant' | 'system';\n text: string;\n /** Streaming \u2192 assistant text still arriving from backend. Complete \u2192 final. Error \u2192 fatal during stream. */\n status?: 'streaming' | 'complete' | 'error';\n /** Tool calls attached to an assistant message (rendered as compact chips). */\n toolCalls?: TrailToolCall[];\n}\n\n/** PRD \u00A74.3 constants. */\nconst DEFAULT_VISIBLE = 3;\nconst OPACITY_STEP = 0.22;\nconst OPACITY_FLOOR = 0.18;\nconst Y_DRIFT_PX = -2;\n/**\n * Height of the absolute blur veil at the top of the trail's scroll\n * area. Pixel-based, NOT message-count-based: with the old per-chip\n * `filter: blur(pos * step)` model, a single long markdown response\n * at pos=1 would have its entire body uniformly blurred \u2014 including\n * the chunk visually adjacent to the crisp newest message \u2014 because\n * blur is a property of the chip element, not of pixels. Switching\n * to a fixed-height backdrop-blur veil keeps the bottom of the\n * visible area (newest content) crisp regardless of message length.\n */\nconst TRAIL_BLUR_VEIL_PX = 72;\n/** Max blur applied at the very top of the veil; tapers to 0 at the bottom edge. */\nconst TRAIL_BLUR_MAX_PX = 5;\n\nexport class AdaptiveChatTrail extends LitElement {\n static override properties = {\n messages: { attribute: false },\n visibleCount: { type: Number },\n expanded: { type: Boolean },\n greeting: { type: String },\n };\n\n messages: TrailMessage[] = [];\n visibleCount = DEFAULT_VISIBLE;\n expanded = false;\n /**\n * Pre-conversation phantom assistant message. Rendered only when\n * `messages` is empty. NOT injected into chatSession state \u2014 the\n * greeting is configuration, not conversation, so it disappears\n * automatically when the first real message arrives.\n */\n greeting: string | undefined = undefined;\n\n override createRenderRoot(): HTMLElement {\n // Light DOM \u2014 canvas-level CSS vars reach the chips directly.\n return this;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n ensureStreamingKeyframes();\n }\n\n private _onExpand = (): void => {\n // Self-manage: clicking the inline link expands the trail in place.\n // We still dispatch the event so any parent that wants to react\n // (telemetry, mirror state to a side panel, etc.) can listen.\n this.expanded = true;\n this.dispatchEvent(new CustomEvent('trail-expand', { bubbles: true, composed: true }));\n };\n\n /**\n * Approve a pending client tool call. The trail emits a generic\n * event so the host can decide whether to forward to chatSession\n * (the common path) or override. Keeps the trail itself free of\n * direct chatSession coupling \u2014 it's a pure view component.\n */\n private _onToolCallClick = (tc: TrailToolCall): void => {\n if (tc.status !== 'pending') return;\n this.dispatchEvent(\n new CustomEvent<{ toolCallId: string; approved: boolean }>('trail-toolcall-approved', {\n detail: { toolCallId: tc.id, approved: true },\n bubbles: true,\n composed: true,\n })\n );\n };\n\n private _onCollapse = (): void => {\n // Mirror of _onExpand for the minimize affordance shown while\n // expanded. Self-manages + dispatches `trail-collapse` for parent\n // observers (telemetry, side-panel mirrors).\n this.expanded = false;\n this.dispatchEvent(new CustomEvent('trail-collapse', { bubbles: true, composed: true }));\n };\n\n override updated(changed: Map<string, unknown>): void {\n // Anchor the scroll to the bottom whenever:\n // - the trail flips into expanded mode (UX continuity), OR\n // - new messages or text deltas arrive (so a long streaming chip\n // stays pinned to the latest line, not the start of the chip).\n // Skip when the user has manually scrolled UP into history \u2014 we'd\n // rather lose pinning than yank them out of what they're reading.\n if (changed.has('expanded') || changed.has('messages')) {\n requestAnimationFrame(() => {\n const container = this.querySelector<HTMLElement>('[data-syntro-chat-trail]');\n if (!container) return;\n const distanceFromBottom =\n container.scrollHeight - container.scrollTop - container.clientHeight;\n // 200px tolerance \u2014 streaming deltas can append a paragraph in\n // one update, easily 80-150px. A tighter window would strand\n // the viewer at the top of the chip mid-stream because the\n // first delta blew past the threshold. User-driven scroll-up\n // of >200px disables auto-pin until they scroll back down.\n if (distanceFromBottom < 200 || changed.has('expanded')) {\n container.scrollTop = container.scrollHeight;\n }\n });\n }\n }\n\n override render() {\n if (this.messages.length === 0 && !this.greeting) return nothing;\n if (this.messages.length === 0 && this.greeting) {\n // Render the greeting as a pure-display assistant chip. No\n // tool-calls, no streaming caret, single chip at pos=0.\n return html`<div data-syntro-chat-trail style=${styleMap({\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n justifyContent: 'flex-end',\n width: '100%',\n padding: '0',\n pointerEvents: 'auto',\n })}>\n <div\n data-trail-chip\n data-role=\"assistant\"\n data-status=\"greeting\"\n style=${styleMap(chipStyles('assistant', 0, { isStreaming: false, isError: false }))}\n >${unsafeHTML(renderMarkdown(stripTrailingWhitespace(this.greeting)))}</div>\n </div>`;\n }\n\n // Choose the slice we actually paint. Newest are at the END of the\n // array (closest to the chat bar). When not expanded, take the last\n // `visibleCount`; when expanded, take all.\n const visible = this.expanded ? this.messages : this.messages.slice(-this.visibleCount);\n const hidden = this.messages.length - visible.length;\n\n const baseStyles: Record<string, string> = {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n // NO `justifyContent: flex-end` here \u2014 combined with `overflow: auto`\n // it's a known cross-browser bug: oversized flex children get\n // pushed ABOVE the container without contributing to scrollHeight,\n // so the scrollbar never appears. We rely on `:first-child {\n // margin-top: auto }` (injected as global CSS) to push short\n // content to the bottom of the box while leaving overflow handling\n // to the native scroll container.\n width: '100%',\n // The trail itself has no chrome \u2014 chips sit on the host page bg.\n padding: '0',\n pointerEvents: 'auto',\n };\n\n // Expanded mode: cap height, scroll, and add a visible top border so\n // the panel reads as a bounded region. NO background tint or shadow\n // \u2014 the ambient (collapsed) treatment is fine and we don't want to\n // suddenly introduce panel chrome that wasn't there before. The\n // border alone marks the panel's top edge.\n // Falloff is turned off per-chip below \u2014 readability wins once the\n // user has explicitly asked for history. See PRD \u00A74.3.\n // Collapsed mode also gets a (looser) cap + scroll so a single long\n // markdown chip doesn't blow the chat-bar off-screen with no\n // scroll affordance. Tighter than expanded so the bubble-up trail\n // doesn't dominate the surface visually.\n const modeStyles: Record<string, string> = this.expanded\n ? {\n maxHeight: 'min(320px, 40vh)',\n overflowY: 'auto',\n paddingTop: '8px',\n borderTop: 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))',\n scrollBehavior: 'smooth',\n scrollbarWidth: 'thin',\n }\n : {\n // Collapsed mode = ambient peek surface. Never a scrollbar:\n // long messages slide up behind the blur veil instead of\n // exposing a scrollbar. The \"\u2191 N more \u00B7 expand\" affordance\n // is the only entry into history; clicking it switches to\n // expanded mode where scroll IS allowed.\n // Height kept short so the trail reads as a peek strip above\n // the chat bar, not a panel \u2014 long single replies fade into\n // the blur veil at the top, user expands to read the rest.\n maxHeight: 'min(140px, 22vh)',\n overflow: 'hidden',\n };\n\n const containerStyles = { ...baseStyles, ...modeStyles };\n\n // Wrap the scroll container in a positioning frame so the absolute\n // blur veil (rendered at the top) anchors to the scroll viewport's\n // top edge \u2014 NOT to a chip that scrolls under it. Pixel-anchored\n // blur means: the top TRAIL_BLUR_VEIL_PX of the visible area is\n // veiled regardless of how many messages occupy those pixels.\n const frameStyles: Record<string, string> = {\n position: 'relative',\n width: '100%',\n };\n\n return html`\n ${\n // Single toggle button above the frame. Same DOM node morphs:\n // collapsed \u2192 \"\u2191 N more \u00B7 expand\" / \"\u2191 expand\" (calls _onExpand)\n // expanded \u2192 \"\u2304 minimize\" (calls _onCollapse)\n // Lives OUTSIDE the frame so the absolute blur veil (top:0,\n // z-index:2 inside the frame) doesn't cover it. Shown whenever\n // there's anything in the trail \u2014 the user expects this affordance\n // available regardless of how many chips are on screen.\n this.messages.length === 0\n ? nothing\n : this.expanded\n ? html`<button\n type=\"button\"\n data-trail-toggle\n data-trail-collapse\n @click=${this._onCollapse}\n style=${styleMap(moreStyles())}\n >\u2304 minimize</button>`\n : html`<button\n type=\"button\"\n data-trail-toggle\n data-trail-more\n @click=${this._onExpand}\n style=${styleMap(moreStyles())}\n >${hidden > 0 ? html`\u2191 ${hidden} more \u00B7 expand` : html`\u2191 expand`}</button>`\n }\n <div data-syntro-chat-trail-frame style=${styleMap(frameStyles)}>\n <div data-syntro-chat-trail style=${styleMap(containerStyles)}>\n ${visible.map((m, i) => {\n // Stack position from the bar: 0 = closest, N-1 = oldest.\n // visible[last] is closest, so reverse the index from the\n // tail of the visible slice. When expanded, the ambient\n // falloff is suppressed (pos = 0) so every chip reads at\n // full opacity \u2014 explicit history view, not ambient trail.\n const pos = this.expanded ? 0 : visible.length - 1 - i;\n const isStreaming = m.status === 'streaming';\n const isError = m.status === 'error' || m.role === 'system';\n const toolCalls = m.toolCalls ?? [];\n // Assistant text is markdown-formatted by the agent (lists,\n // code, bold, links). renderMarkdown sanitizes via DOMPurify,\n // then unsafeHTML injects the safe HTML. User + system chips\n // stay plain text \u2014 no markdown risk + no expansion attack\n // surface on user-typed input.\n const renderedText =\n m.role === 'assistant'\n ? html`${unsafeHTML(renderMarkdown(stripTrailingWhitespace(m.text)))}`\n : html`${m.text}`;\n return html`<div\n data-trail-chip\n data-role=${m.role}\n data-status=${m.status ?? 'complete'}\n style=${styleMap(chipStyles(m.role, pos, { isStreaming, isError }))}\n >${renderedText}${\n isStreaming\n ? html`<span\n data-trail-caret\n aria-hidden=\"true\"\n style=${styleMap(caretStyles())}\n ></span>`\n : nothing\n }${\n toolCalls.length > 0\n ? html`<div data-trail-toolcalls style=${styleMap(toolCallStripStyles())}>\n ${toolCalls.map(\n (tc) => html`<button\n type=\"button\"\n data-trail-toolcall\n data-tool-id=${tc.id}\n data-tool-status=${tc.status}\n @click=${() => this._onToolCallClick(tc)}\n ?disabled=${tc.status !== 'pending'}\n style=${styleMap(toolCallChipStyles(tc.status))}\n title=\"${tc.name} \u00B7 ${tc.status}\"\n >${toolCallIcon(tc.status)} ${tc.name}</button>`\n )}\n </div>`\n : nothing\n }</div>`;\n })}\n </div>\n ${\n // The blur veil only fires in collapsed (ambient) mode.\n // Expanded = the user explicitly asked for history; blurring\n // the top of the panel would hide content (including the\n // minimize button just above the trail's top edge) the user\n // came here to see.\n this.expanded\n ? nothing\n : html`<div\n data-trail-blur-veil\n aria-hidden=\"true\"\n style=${styleMap(blurVeilStyles())}\n ></div>`\n }\n </div>\n `;\n }\n}\n\n/**\n * Absolute blur veil at the top of the scroll viewport. Uses\n * `backdrop-filter: blur(...)` so whatever pixels sit beneath the veil\n * (top of the scroll content) become blurred, regardless of which chip\n * those pixels belong to. A linear mask fades the blur effect from full\n * at the top to zero at the bottom of the veil \u2014 newest content stays\n * crisp because it lives below the veil's height.\n *\n * Note: `mask-image` controls where the BACKDROP-FILTER applies (via\n * masking the veil element), so the bottom edge of the veil naturally\n * dissolves into the unblurred scroll area below.\n */\nfunction blurVeilStyles(): Record<string, string> {\n const mask = 'linear-gradient(to bottom, black 0%, black 40%, transparent 100%)';\n return {\n position: 'absolute',\n top: '0',\n left: '0',\n right: '0',\n height: `${TRAIL_BLUR_VEIL_PX}px`,\n pointerEvents: 'none',\n backdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,\n WebkitBackdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,\n maskImage: mask,\n WebkitMaskImage: mask,\n zIndex: '2',\n };\n}\n\nfunction chipStyles(\n role: TrailMessage['role'],\n pos: number,\n state: { isStreaming: boolean; isError: boolean } = { isStreaming: false, isError: false }\n): Record<string, string> {\n const opacity = Math.max(OPACITY_FLOOR, 1 - pos * OPACITY_STEP);\n const yPx = pos * Y_DRIFT_PX;\n\n // Error chips override role-specific styling \u2014 they shouldn't read\n // as \"an assistant reply.\" Streaming chips get a subtle accent ring\n // so the in-progress state is visually distinct from a settled reply.\n // All four states resolve to design-system tokens via the theme; the\n // fallbacks here are conservative neutrals so the widget still renders\n // sensibly on a host page that hasn't set tokens yet.\n const border = state.isError\n ? 'var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.55))'\n : state.isStreaming\n ? 'var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))'\n : role === 'user'\n ? 'var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))'\n : 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))';\n const background = state.isError\n ? 'var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.42))'\n : role === 'user'\n ? 'var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))'\n : 'var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))';\n\n const base: Record<string, string> = {\n alignSelf: role === 'user' ? 'flex-end' : 'flex-start',\n maxWidth: '85%',\n fontSize: '11px',\n lineHeight: '1.45',\n padding: '4px 10px',\n borderRadius: '10px',\n border,\n background,\n backdropFilter: 'blur(8px)',\n WebkitBackdropFilter: 'blur(8px)',\n color: state.isError\n ? 'var(--sc-content-bubble-text-error, rgba(255, 220, 220, 0.95))'\n : 'var(--sc-tile-title-color, inherit)',\n transition:\n 'opacity 240ms cubic-bezier(0.22, 1, 0.36, 1), transform 240ms cubic-bezier(0.22, 1, 0.36, 1)',\n // Per-position opacity + y-drift falloff is still per-chip \u2014 those\n // operate naturally per element. Blur is NOT per-chip anymore: it's\n // applied via an absolute-positioned veil at the top of the scroll\n // viewport (TRAIL_BLUR_VEIL_PX above), so a single long message\n // doesn't get uniformly blurred across its full body.\n opacity: String(toFixedTrim(opacity)),\n transform: `translateY(${yPx}px)`,\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n };\n return base;\n}\n\nfunction toolCallStripStyles(): Record<string, string> {\n return {\n display: 'flex',\n flexWrap: 'wrap',\n gap: '4px',\n marginTop: '4px',\n paddingTop: '4px',\n borderTop: '1px dashed var(--sc-content-divider-color, rgba(255, 255, 255, 0.12))',\n };\n}\n\nfunction toolCallChipStyles(status: TrailToolCall['status']): Record<string, string> {\n const isPending = status === 'pending';\n const isError = status === 'error';\n const isDone = status === 'done';\n return {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n padding: '2px 7px',\n border: isError\n ? 'var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.45))'\n : isPending\n ? 'var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))'\n : 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))',\n background: isError\n ? 'var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.35))'\n : isPending\n ? 'var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))'\n : isDone\n ? 'var(--sc-content-bubble-background-idle, rgba(40, 44, 50, 0.4))'\n : 'var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))',\n color: 'var(--sc-tile-text-color, inherit)',\n borderRadius: '999px',\n fontSize: '10px',\n fontWeight: '500',\n fontFamily: 'ui-monospace, SF Mono, Menlo, monospace',\n cursor: isPending ? 'pointer' : 'default',\n opacity: isDone ? '0.7' : '1',\n };\n}\n\nfunction toolCallIcon(status: TrailToolCall['status']): string {\n switch (status) {\n case 'args-streaming':\n case 'running':\n return '\u22EF';\n case 'pending':\n return '?';\n case 'done':\n return '\u2713';\n case 'error':\n return '\u2717';\n default:\n return '\u00B7';\n }\n}\n\nfunction caretStyles(): Record<string, string> {\n return {\n display: 'inline-block',\n width: '6px',\n height: '12px',\n marginLeft: '3px',\n verticalAlign: '-1px',\n background: 'hsl(var(--sc-accent-color) / 0.85)',\n borderRadius: '1px',\n animation: 'syntro-trail-caret 1s steps(2, end) infinite',\n };\n}\n\nfunction moreStyles(): Record<string, string> {\n return {\n // Center horizontally regardless of the parent's display model.\n // ``alignSelf: center`` only works when the parent is a flex/grid\n // container with cross-axis alignment; the chat-bar's flex column\n // stretches us full-width instead. ``display: block`` + auto inline\n // margins centers the auto-sized button without needing the parent\n // to opt in.\n display: 'block',\n marginLeft: 'auto',\n marginRight: 'auto',\n marginBottom: '6px',\n fontSize: '10px',\n fontWeight: '500',\n letterSpacing: '0.06em',\n padding: '4px 10px',\n border: '1px solid hsl(var(--sc-accent-color) / 0.32)',\n borderRadius: '999px',\n background: 'hsl(var(--sc-accent-color) / 0.10)',\n color: 'var(--sc-tile-text-color, currentColor)',\n cursor: 'pointer',\n opacity: '0.85',\n backdropFilter: 'blur(8px)',\n WebkitBackdropFilter: 'blur(8px)',\n transition: 'opacity 150ms ease, background 150ms ease',\n };\n}\n\n/**\n * Inject the streaming-caret keyframes once per document. Light-DOM\n * components can't ship CSS via Lit's `styles` static \u2014 the rules\n * need to be present in the document's stylesheet. Idempotent via\n * a data attribute on the injected style tag.\n */\nfunction ensureStreamingKeyframes(): void {\n if (typeof document === 'undefined') return;\n if (document.head.querySelector('style[data-syntro-trail-caret-keyframes]')) return;\n const style = document.createElement('style');\n style.setAttribute('data-syntro-trail-caret-keyframes', 'true');\n // Caret animation + markdown reset for assistant chips. The trail\n // chips are tiny (11px line-height); default <p>/<ul>/<pre> margins\n // would push the chip vertically and break the falloff layout.\n style.textContent = [\n '@keyframes syntro-trail-caret { 0%{opacity:1} 50%{opacity:0} 100%{opacity:1} }',\n // Anchor short content to the bottom of the scrollable trail \u2014\n // see baseStyles comment for why we can't use justify-content.\n '[data-syntro-chat-trail] > :first-child { margin-top: auto }',\n // Tight spacing \u2014 trail chips are an 11px-line-height pill. Default\n // browser <p>/<ul> margins (1em \u2248 11px each side) would dominate the\n // chip. Keep paragraph + list separators minimal (4px) so multi-\n // paragraph replies read as one continuous flow, not a vertical\n // stack of fragments.\n '[data-trail-chip] > p:first-child { margin-top: 0 }',\n '[data-trail-chip] > p:last-child { margin-bottom: 0 }',\n '[data-trail-chip] p { margin: 4px 0 }',\n '[data-trail-chip] br + br { display: none }',\n '[data-trail-chip] ul, [data-trail-chip] ol { margin: 4px 0; padding-left: 1.1em }',\n '[data-trail-chip] li { margin: 0 }',\n '[data-trail-chip] li + li { margin-top: 1px }',\n '[data-trail-chip] pre { margin: 6px 0; padding: 6px 8px; background: var(--sc-content-code-background-block, rgba(0, 0, 0, 0.35)); border-radius: 6px; overflow-x: auto; font-size: 10px }',\n '[data-trail-chip] code { font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: 10px; background: var(--sc-content-code-background, rgba(0, 0, 0, 0.25)); padding: 1px 4px; border-radius: 3px }',\n '[data-trail-chip] pre code { background: transparent; padding: 0 }',\n '[data-trail-chip] a { color: var(--sc-content-link-color, var(--sc-color-primary, #b72e2a)); text-decoration: underline }',\n '[data-trail-chip] strong { font-weight: 600 }',\n ].join(' ');\n document.head.appendChild(style);\n}\n\n/**\n * Pre-process LLM markdown before rendering. Three common artifacts:\n *\n * 1. Trailing \" \" (two spaces = markdown hard-break) sprinkled at\n * end-of-line, even inside list items where it just leaks\n * invisible whitespace into the DOM.\n * 2. Runs of 3+ blank lines used as visual separators. Markdown\n * treats any number of blank lines as a single paragraph break,\n * so the extras add nothing semantically \u2014 but combined with\n * paragraph margins they create huge gaps in the tiny chip layout.\n * 3. Leading / trailing newlines on the whole message. Trailing\n * `\\n\\n` is the common one \u2014 marked turns it into a final empty\n * <p> that still occupies a full line-height of vertical space,\n * leaving a visible gap between the last text and the chat bar.\n *\n * Collapsing all three keeps the rendered HTML tight without changing\n * the agent's semantic intent.\n */\nfunction stripTrailingWhitespace(text: string): string {\n return text\n .replace(/[ \\t]+$/gm, '') // trailing whitespace per line\n .replace(/\\n{3,}/g, '\\n\\n') // collapse runs of blank lines\n .trim(); // drop leading/trailing whitespace+newlines on the whole message\n}\n\n/** Two-decimal trim that avoids trailing zeros (\".50\" \u2192 \".5\"). */\nfunction toFixedTrim(n: number): string {\n const s = n.toFixed(2);\n return s.replace(/\\.?0+$/, '') || '0';\n}\n\nif (!customElements.get('adaptive-chat-trail')) {\n customElements.define('adaptive-chat-trail', AdaptiveChatTrail);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'adaptive-chat-trail': AdaptiveChatTrail;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }