@tangle-network/sandbox-ui 0.54.0 → 0.56.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.
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-GTJ5PSK5.js";
4
4
  import {
5
5
  buildWorkflowGraph
6
- } from "./chunk-DEN4GUA7.js";
6
+ } from "./chunk-WEYFEMIN.js";
7
7
  import {
8
8
  providerLabel
9
9
  } from "./chunk-7JLB7UZ3.js";
package/dist/assistant.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  ProviderIcon
3
3
  } from "./chunk-GTJ5PSK5.js";
4
+ import {
5
+ timeAgo
6
+ } from "./chunk-TK46XFLM.js";
4
7
  import {
5
8
  providerLabel
6
9
  } from "./chunk-7JLB7UZ3.js";
@@ -1542,11 +1545,115 @@ import {
1542
1545
 
1543
1546
  // src/assistant/AssistantPanel.tsx
1544
1547
  import { AgentTimeline, ChatInput, ThinkingIndicator } from "@tangle-network/ui/chat";
1545
- import { History, MessageSquarePlus, Minus, Plus, Trash2, X } from "lucide-react";
1546
- import { useEffect as useEffect5, useMemo, useRef as useRef4, useState as useState5 } from "react";
1548
+ import { History, MessageSquarePlus, Minus, Plus, X } from "lucide-react";
1549
+ import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef4, useState as useState6 } from "react";
1550
+
1551
+ // src/assistant/AssistantHistory.tsx
1552
+ import { Search, Trash2 } from "lucide-react";
1553
+ import { useMemo, useState as useState3 } from "react";
1554
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1555
+ function parsedTime(iso) {
1556
+ if (!iso) return null;
1557
+ const ms = Date.parse(iso);
1558
+ return Number.isFinite(ms) ? ms : null;
1559
+ }
1560
+ function AssistantHistory({
1561
+ threads,
1562
+ loaded,
1563
+ activeThreadId,
1564
+ activeBusy,
1565
+ canRemove,
1566
+ onSelect,
1567
+ onDelete
1568
+ }) {
1569
+ const [query, setQuery] = useState3("");
1570
+ const sorted = useMemo(
1571
+ () => [...threads].sort((a, b) => {
1572
+ const ta = parsedTime(a.updatedAt);
1573
+ const tb = parsedTime(b.updatedAt);
1574
+ if (ta === null && tb === null) return 0;
1575
+ if (ta === null) return 1;
1576
+ if (tb === null) return -1;
1577
+ return tb - ta;
1578
+ }),
1579
+ [threads]
1580
+ );
1581
+ const trimmed = query.trim().toLowerCase();
1582
+ const visible = useMemo(
1583
+ () => trimmed ? (
1584
+ // Match the title as displayed, so searching "untitled" finds the
1585
+ // rows that render as "Untitled conversation".
1586
+ sorted.filter(
1587
+ (t) => (t.title ?? "Untitled conversation").toLowerCase().includes(trimmed)
1588
+ )
1589
+ ) : sorted,
1590
+ [sorted, trimmed]
1591
+ );
1592
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
1593
+ /* @__PURE__ */ jsx2("div", { className: "border-border border-b p-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1594
+ /* @__PURE__ */ jsx2(Search, { className: "-translate-y-1/2 pointer-events-none absolute top-1/2 left-2.5 h-3.5 w-3.5 text-muted-foreground" }),
1595
+ /* @__PURE__ */ jsx2(
1596
+ "input",
1597
+ {
1598
+ type: "search",
1599
+ value: query,
1600
+ onChange: (e) => setQuery(e.target.value),
1601
+ placeholder: "Search conversations",
1602
+ "aria-label": "Search conversations",
1603
+ className: "w-full rounded-md border border-border bg-surface-container-high py-1.5 pr-2 pl-8 text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1604
+ }
1605
+ )
1606
+ ] }) }),
1607
+ /* @__PURE__ */ jsx2("div", { className: "min-h-0 flex-1 overflow-y-auto", children: visible.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "px-3 py-6 text-center text-muted-foreground text-xs", children: !loaded ? "Loading\u2026" : trimmed ? "No conversations match your search." : "No past conversations yet." }) : /* @__PURE__ */ jsx2("ul", { className: "py-1", children: visible.map((t) => {
1608
+ const active = t.id === activeThreadId;
1609
+ const ms = parsedTime(t.updatedAt);
1610
+ const busyActive = active && activeBusy;
1611
+ const title = t.title ?? "Untitled conversation";
1612
+ return /* @__PURE__ */ jsxs(
1613
+ "li",
1614
+ {
1615
+ className: `group flex items-center transition-colors hover:bg-muted/60 ${active ? "bg-primary/10" : ""}`,
1616
+ children: [
1617
+ /* @__PURE__ */ jsxs(
1618
+ "button",
1619
+ {
1620
+ type: "button",
1621
+ onClick: () => onSelect(t.id),
1622
+ className: "flex min-w-0 flex-1 flex-col gap-0.5 px-3 py-2 text-left",
1623
+ children: [
1624
+ /* @__PURE__ */ jsx2(
1625
+ "span",
1626
+ {
1627
+ className: `truncate text-sm ${active ? "font-medium text-foreground" : "text-foreground"}`,
1628
+ children: title
1629
+ }
1630
+ ),
1631
+ ms != null && /* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: timeAgo(ms) })
1632
+ ]
1633
+ }
1634
+ ),
1635
+ canRemove && /* @__PURE__ */ jsx2(
1636
+ "button",
1637
+ {
1638
+ type: "button",
1639
+ onClick: () => onDelete(t.id),
1640
+ disabled: busyActive,
1641
+ "aria-label": `Delete conversation: ${title}`,
1642
+ title: busyActive ? "Can't delete while this conversation is active" : "Delete conversation",
1643
+ className: "shrink-0 p-2 text-muted-foreground opacity-0 transition [@media(hover:none)]:opacity-100 hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30",
1644
+ children: /* @__PURE__ */ jsx2(Trash2, { className: "h-3.5 w-3.5" })
1645
+ }
1646
+ )
1647
+ ]
1648
+ },
1649
+ t.id
1650
+ );
1651
+ }) }) })
1652
+ ] });
1653
+ }
1547
1654
 
1548
1655
  // src/assistant/build-timeline.tsx
1549
- import { jsx as jsx2 } from "react/jsx-runtime";
1656
+ import { jsx as jsx3 } from "react/jsx-runtime";
1550
1657
  var TOOL_STATUS = {
1551
1658
  running: "running",
1552
1659
  ok: "success",
@@ -1602,7 +1709,7 @@ function buildAssistantTimeline(state, renderProposal) {
1602
1709
  items.push({
1603
1710
  id: "reasoning",
1604
1711
  kind: "custom",
1605
- content: /* @__PURE__ */ jsx2("p", { className: "whitespace-pre-wrap px-1 text-muted-foreground text-xs italic", children: state.reasoning })
1712
+ content: /* @__PURE__ */ jsx3("p", { className: "whitespace-pre-wrap px-1 text-muted-foreground text-xs italic", children: state.reasoning })
1606
1713
  });
1607
1714
  }
1608
1715
  if (state.status !== "streaming" && state.usage && state.usage.costUsd != null) {
@@ -1623,8 +1730,8 @@ function buildAssistantTimeline(state, renderProposal) {
1623
1730
  }
1624
1731
 
1625
1732
  // src/assistant/ProposalCard.tsx
1626
- import { useState as useState3 } from "react";
1627
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
1733
+ import { useState as useState4 } from "react";
1734
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1628
1735
  function ProposalCard({
1629
1736
  proposal,
1630
1737
  confirming,
@@ -1634,31 +1741,31 @@ function ProposalCard({
1634
1741
  renderGraph
1635
1742
  }) {
1636
1743
  const view = describeProposal(proposal);
1637
- const [tab, setTab] = useState3("graph");
1744
+ const [tab, setTab] = useState4("graph");
1638
1745
  const isWorkflow = view.preview?.kind === "workflow";
1639
1746
  const showGraph = isWorkflow && !!renderGraph;
1640
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-primary/40 bg-card p-3 text-sm", children: [
1641
- /* @__PURE__ */ jsx3("p", { className: "font-medium text-foreground", children: view.title }),
1642
- /* @__PURE__ */ jsx3("p", { className: "text-muted-foreground text-xs", children: "Confirm to run this action on your account." }),
1643
- view.fields.length > 0 && /* @__PURE__ */ jsx3("dl", { className: "mt-2 space-y-1", children: view.fields.map((f) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs", children: [
1644
- /* @__PURE__ */ jsx3("dt", { className: "shrink-0 text-muted-foreground", children: f.label }),
1645
- /* @__PURE__ */ jsx3("dd", { className: "truncate text-foreground", title: f.value, children: f.value })
1747
+ return /* @__PURE__ */ jsxs2("div", { className: "rounded-lg border border-primary/40 bg-card p-3 text-sm", children: [
1748
+ /* @__PURE__ */ jsx4("p", { className: "font-medium text-foreground", children: view.title }),
1749
+ /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground text-xs", children: "Confirm to run this action on your account." }),
1750
+ view.fields.length > 0 && /* @__PURE__ */ jsx4("dl", { className: "mt-2 space-y-1", children: view.fields.map((f) => /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 text-xs", children: [
1751
+ /* @__PURE__ */ jsx4("dt", { className: "shrink-0 text-muted-foreground", children: f.label }),
1752
+ /* @__PURE__ */ jsx4("dd", { className: "truncate text-foreground", title: f.value, children: f.value })
1646
1753
  ] }, f.label)) }),
1647
- view.skills && view.skills.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
1648
- /* @__PURE__ */ jsx3("p", { className: "text-muted-foreground text-xs", children: "New skills" }),
1649
- /* @__PURE__ */ jsx3("ul", { className: "mt-1 space-y-0.5", children: view.skills.map((s) => /* @__PURE__ */ jsxs("li", { className: "text-foreground text-xs", children: [
1650
- /* @__PURE__ */ jsx3("span", { className: "font-medium", children: s.name }),
1651
- s.description ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1754
+ view.skills && view.skills.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "mt-2", children: [
1755
+ /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground text-xs", children: "New skills" }),
1756
+ /* @__PURE__ */ jsx4("ul", { className: "mt-1 space-y-0.5", children: view.skills.map((s) => /* @__PURE__ */ jsxs2("li", { className: "text-foreground text-xs", children: [
1757
+ /* @__PURE__ */ jsx4("span", { className: "font-medium", children: s.name }),
1758
+ s.description ? /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
1652
1759
  " \u2014 ",
1653
1760
  s.description
1654
1761
  ] }) : null
1655
1762
  ] }, s.name)) })
1656
1763
  ] }),
1657
- view.preview && /* @__PURE__ */ jsxs("div", { className: "mt-2", children: [
1658
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1659
- /* @__PURE__ */ jsx3("p", { className: "text-muted-foreground text-xs", children: view.preview.label }),
1660
- showGraph && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 text-xs", children: [
1661
- /* @__PURE__ */ jsx3(
1764
+ view.preview && /* @__PURE__ */ jsxs2("div", { className: "mt-2", children: [
1765
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
1766
+ /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground text-xs", children: view.preview.label }),
1767
+ showGraph && /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 text-xs", children: [
1768
+ /* @__PURE__ */ jsx4(
1662
1769
  "button",
1663
1770
  {
1664
1771
  type: "button",
@@ -1667,7 +1774,7 @@ function ProposalCard({
1667
1774
  children: "Graph"
1668
1775
  }
1669
1776
  ),
1670
- /* @__PURE__ */ jsx3(
1777
+ /* @__PURE__ */ jsx4(
1671
1778
  "button",
1672
1779
  {
1673
1780
  type: "button",
@@ -1678,11 +1785,11 @@ function ProposalCard({
1678
1785
  )
1679
1786
  ] })
1680
1787
  ] }),
1681
- showGraph && tab === "graph" ? /* @__PURE__ */ jsx3("div", { className: "mt-1 h-64 overflow-hidden rounded border border-border", children: renderGraph?.(view.preview.content) }) : /* @__PURE__ */ jsx3("pre", { className: "mt-1 max-h-48 overflow-auto rounded border border-border bg-muted/50 p-2 text-xs", children: /* @__PURE__ */ jsx3("code", { children: view.preview.content }) })
1788
+ showGraph && tab === "graph" ? /* @__PURE__ */ jsx4("div", { className: "mt-1 h-64 overflow-hidden rounded border border-border", children: renderGraph?.(view.preview.content) }) : /* @__PURE__ */ jsx4("pre", { className: "mt-1 max-h-48 overflow-auto rounded border border-border bg-muted/50 p-2 text-xs", children: /* @__PURE__ */ jsx4("code", { children: view.preview.content }) })
1682
1789
  ] }),
1683
- proposal.requirements && proposal.requirements.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded border border-border p-2", children: [
1684
- /* @__PURE__ */ jsx3("p", { className: "text-muted-foreground text-xs", children: "Integrations" }),
1685
- /* @__PURE__ */ jsx3("ul", { className: "mt-1 space-y-1", children: proposal.requirements.map((r) => /* @__PURE__ */ jsx3(
1790
+ proposal.requirements && proposal.requirements.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "mt-3 rounded border border-border p-2", children: [
1791
+ /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground text-xs", children: "Integrations" }),
1792
+ /* @__PURE__ */ jsx4("ul", { className: "mt-1 space-y-1", children: proposal.requirements.map((r) => /* @__PURE__ */ jsx4(
1686
1793
  RequirementRow,
1687
1794
  {
1688
1795
  req: r,
@@ -1690,11 +1797,11 @@ function ProposalCard({
1690
1797
  },
1691
1798
  `${r.provider}-${r.kind ?? "integration"}`
1692
1799
  )) }),
1693
- /* @__PURE__ */ jsx3("p", { className: "mt-1 text-muted-foreground text-xs", children: "Connect the items above, then confirm \u2014 your proposal stays here until you do." })
1800
+ /* @__PURE__ */ jsx4("p", { className: "mt-1 text-muted-foreground text-xs", children: "Connect the items above, then confirm \u2014 your proposal stays here until you do." })
1694
1801
  ] }),
1695
- proposal.retryError && /* @__PURE__ */ jsx3("p", { role: "alert", className: "mt-2 text-destructive text-xs", children: proposal.retryError }),
1696
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex gap-2", children: [
1697
- /* @__PURE__ */ jsx3(
1802
+ proposal.retryError && /* @__PURE__ */ jsx4("p", { role: "alert", className: "mt-2 text-destructive text-xs", children: proposal.retryError }),
1803
+ /* @__PURE__ */ jsxs2("div", { className: "mt-3 flex gap-2", children: [
1804
+ /* @__PURE__ */ jsx4(
1698
1805
  "button",
1699
1806
  {
1700
1807
  type: "button",
@@ -1704,7 +1811,7 @@ function ProposalCard({
1704
1811
  children: confirming ? "Confirming\u2026" : "Confirm"
1705
1812
  }
1706
1813
  ),
1707
- /* @__PURE__ */ jsx3(
1814
+ /* @__PURE__ */ jsx4(
1708
1815
  "button",
1709
1816
  {
1710
1817
  type: "button",
@@ -1744,9 +1851,9 @@ function RequirementRow({
1744
1851
  const statusText = req.connected ? isApp ? "installed" : "connected" : isApp ? "not installed" : "not connected";
1745
1852
  const canConnect = !req.connected && req.connectUrl !== null;
1746
1853
  const target = req.connectUrl ?? "/app/integrations";
1747
- return /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2 text-xs", children: [
1748
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
1749
- /* @__PURE__ */ jsx3(
1854
+ return /* @__PURE__ */ jsxs2("li", { className: "flex items-center justify-between gap-2 text-xs", children: [
1855
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-2", children: [
1856
+ /* @__PURE__ */ jsx4(
1750
1857
  ProviderIcon,
1751
1858
  {
1752
1859
  id: req.provider,
@@ -1755,16 +1862,16 @@ function RequirementRow({
1755
1862
  className: "rounded"
1756
1863
  }
1757
1864
  ),
1758
- /* @__PURE__ */ jsx3("span", { className: "truncate text-foreground", children: kindLabel }),
1759
- /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
1760
- /* @__PURE__ */ jsx3(
1865
+ /* @__PURE__ */ jsx4("span", { className: "truncate text-foreground", children: kindLabel }),
1866
+ /* @__PURE__ */ jsxs2("span", { className: "flex shrink-0 items-center gap-1", children: [
1867
+ /* @__PURE__ */ jsx4(
1761
1868
  "span",
1762
1869
  {
1763
1870
  "aria-hidden": "true",
1764
1871
  className: `h-1.5 w-1.5 rounded-full ${req.connected ? "bg-primary" : "border border-muted-foreground"}`
1765
1872
  }
1766
1873
  ),
1767
- /* @__PURE__ */ jsx3(
1874
+ /* @__PURE__ */ jsx4(
1768
1875
  "span",
1769
1876
  {
1770
1877
  className: req.connected ? "text-primary" : "text-muted-foreground",
@@ -1773,7 +1880,7 @@ function RequirementRow({
1773
1880
  )
1774
1881
  ] })
1775
1882
  ] }),
1776
- canConnect && /* @__PURE__ */ jsxs(
1883
+ canConnect && /* @__PURE__ */ jsxs2(
1777
1884
  "button",
1778
1885
  {
1779
1886
  type: "button",
@@ -1789,7 +1896,7 @@ function RequirementRow({
1789
1896
  }
1790
1897
 
1791
1898
  // src/assistant/usePanelPrefs.ts
1792
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
1899
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
1793
1900
  var MIN_PANEL_WIDTH = 360;
1794
1901
  var DEFAULT_PANEL_WIDTH = 448;
1795
1902
  var MAX_PANEL_WIDTH_FRACTION = 0.95;
@@ -1830,8 +1937,8 @@ function clampScale(value) {
1830
1937
  return Math.min(Math.max(stepped, MIN_FONT_SCALE), MAX_FONT_SCALE);
1831
1938
  }
1832
1939
  function usePanelWidth() {
1833
- const [width, setWidthState] = useState4(DEFAULT_PANEL_WIDTH);
1834
- const [maxWidth, setMaxWidth] = useState4(() => maxPanelWidth());
1940
+ const [width, setWidthState] = useState5(DEFAULT_PANEL_WIDTH);
1941
+ const [maxWidth, setMaxWidth] = useState5(() => maxPanelWidth());
1835
1942
  const desiredRef = useRef3(DEFAULT_PANEL_WIDTH);
1836
1943
  useEffect4(() => {
1837
1944
  setMaxWidth(maxPanelWidth());
@@ -1867,7 +1974,7 @@ function usePanelWidth() {
1867
1974
  return { width, maxWidth, setWidth, previewWidth, nudgeWidth };
1868
1975
  }
1869
1976
  function useFontScale() {
1870
- const [scale, setScaleState] = useState4(DEFAULT_FONT_SCALE);
1977
+ const [scale, setScaleState] = useState5(DEFAULT_FONT_SCALE);
1871
1978
  useEffect4(() => {
1872
1979
  const stored = readNumber(FONT_SCALE_KEY);
1873
1980
  if (stored != null) setScaleState(clampScale(stored));
@@ -1888,7 +1995,7 @@ function useFontScale() {
1888
1995
  };
1889
1996
  }
1890
1997
  function useIsDesktop() {
1891
- const [isDesktop, setIsDesktop] = useState4(
1998
+ const [isDesktop, setIsDesktop] = useState5(
1892
1999
  () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(min-width: 768px)").matches : true
1893
2000
  );
1894
2001
  useEffect4(() => {
@@ -1905,7 +2012,7 @@ function useIsDesktop() {
1905
2012
  }
1906
2013
 
1907
2014
  // src/assistant/AssistantPanel.tsx
1908
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
2015
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1909
2016
  var EMPTY_STATE = "Ask me to create a workflow, check your usage, or manage your API keys.";
1910
2017
  function defaultFormatMoney(usd) {
1911
2018
  if (usd == null) return "\u2014";
@@ -1944,10 +2051,10 @@ function AssistantPanel({
1944
2051
  const models = useAssistantModels();
1945
2052
  const threads = useAssistantThreads(userId);
1946
2053
  const font = useFontScale();
1947
- const [historyOpen, setHistoryOpen] = useState5(false);
1948
- const historyRef = useRef4(null);
2054
+ const [view, setView] = useState6("chat");
1949
2055
  const historyButtonRef = useRef4(null);
1950
- const pickerModels = useMemo(
2056
+ const logRef = useRef4(null);
2057
+ const pickerModels = useMemo2(
1951
2058
  () => toPickerModels(models, chat.selectedModel),
1952
2059
  [models, chat.selectedModel]
1953
2060
  );
@@ -1956,30 +2063,27 @@ function AssistantPanel({
1956
2063
  const chatRef = useRef4(chat);
1957
2064
  chatRef.current = chat;
1958
2065
  useEffect5(() => {
1959
- if (!historyOpen) return;
1960
- const onPointerDown = (e) => {
1961
- const target = e.target;
1962
- if (historyRef.current?.contains(target)) return;
1963
- if (historyButtonRef.current?.contains(target)) return;
1964
- setHistoryOpen(false);
1965
- };
2066
+ if (view !== "history") return;
2067
+ const search = logRef.current?.querySelector(
2068
+ 'input[type="search"]'
2069
+ );
2070
+ (search ?? logRef.current)?.focus();
2071
+ }, [view]);
2072
+ useEffect5(() => {
2073
+ if (view !== "history") return;
1966
2074
  const onKeyDownCapture = (e) => {
1967
2075
  if (e.key !== "Escape") return;
1968
- if (!historyRef.current?.contains(e.target)) return;
2076
+ const target = e.target;
2077
+ if (!logRef.current?.contains(target) && !historyButtonRef.current?.contains(target)) {
2078
+ return;
2079
+ }
1969
2080
  e.stopImmediatePropagation();
1970
- setHistoryOpen(false);
2081
+ setView("chat");
1971
2082
  historyButtonRef.current?.focus();
1972
2083
  };
1973
- document.addEventListener("pointerdown", onPointerDown);
1974
2084
  document.addEventListener("keydown", onKeyDownCapture, true);
1975
- return () => {
1976
- document.removeEventListener("pointerdown", onPointerDown);
1977
- document.removeEventListener("keydown", onKeyDownCapture, true);
1978
- };
1979
- }, [historyOpen]);
1980
- useEffect5(() => {
1981
- if (historyOpen) historyRef.current?.focus();
1982
- }, [historyOpen]);
2085
+ return () => document.removeEventListener("keydown", onKeyDownCapture, true);
2086
+ }, [view]);
1983
2087
  const effectiveBalance = state.usage?.balanceUsd ?? balanceUsd;
1984
2088
  const errorView = state.error ? presentError(state.error.code, state.error.message) : null;
1985
2089
  const low = isLowBalance(effectiveBalance) && !errorView;
@@ -1987,7 +2091,7 @@ function AssistantPanel({
1987
2091
  const firstUserText = state.messages.find((m) => m.role === "user")?.text.trim();
1988
2092
  const titleChars = firstUserText ? Array.from(firstUserText) : [];
1989
2093
  const conversationTitle = firstUserText ? titleChars.length > 60 ? `${titleChars.slice(0, 60).join("")}\u2026` : firstUserText : null;
1990
- const renderProposal = (proposal) => /* @__PURE__ */ jsx4(
2094
+ const renderProposal = (proposal) => /* @__PURE__ */ jsx5(
1991
2095
  ProposalCard,
1992
2096
  {
1993
2097
  proposal,
@@ -1999,12 +2103,13 @@ function AssistantPanel({
1999
2103
  }
2000
2104
  );
2001
2105
  const isThinking = assistantIsThinking(state);
2002
- const openHistory = () => {
2003
- setHistoryOpen((v) => {
2004
- const next = !v;
2005
- if (next) threads.refresh();
2006
- return next;
2007
- });
2106
+ const showHistory = () => {
2107
+ threads.refresh();
2108
+ setView("history");
2109
+ };
2110
+ const toggleHistory = () => {
2111
+ if (view === "history") setView("chat");
2112
+ else showHistory();
2008
2113
  };
2009
2114
  const deleteThread = async (threadId) => {
2010
2115
  const pre = chatRef.current.state;
@@ -2018,85 +2123,38 @@ function AssistantPanel({
2018
2123
  chatRef.current.reset();
2019
2124
  }
2020
2125
  };
2021
- return /* @__PURE__ */ jsxs2("div", { className: "relative flex h-full flex-col bg-background", children: [
2022
- /* @__PURE__ */ jsxs2("div", { className: "relative z-10 border-border border-b", children: [
2023
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-4 pt-3 pb-1.5", children: [
2024
- /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col", children: [
2025
- /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-2", children: [
2026
- /* @__PURE__ */ jsx4("span", { className: "font-medium text-foreground text-sm", children: "Assistant" }),
2027
- /* @__PURE__ */ jsx4(
2028
- "span",
2029
- {
2030
- "aria-label": "Your credit balance",
2031
- className: "text-muted-foreground text-xs",
2032
- children: formatMoney(effectiveBalance)
2033
- }
2034
- )
2035
- ] }),
2036
- conversationTitle && /* @__PURE__ */ jsx4(
2126
+ return /* @__PURE__ */ jsxs3("div", { className: "relative flex h-full flex-col bg-background", children: [
2127
+ /* @__PURE__ */ jsx5("div", { className: "border-border border-b", children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-2 px-4 pt-3 pb-2.5", children: [
2128
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-col", children: [
2129
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-baseline gap-2", children: [
2130
+ /* @__PURE__ */ jsx5("span", { className: "font-medium text-foreground text-sm", children: "Assistant" }),
2131
+ /* @__PURE__ */ jsx5(
2037
2132
  "span",
2038
2133
  {
2039
- className: "truncate text-muted-foreground text-xs",
2040
- title: conversationTitle,
2041
- children: conversationTitle
2134
+ "aria-label": "Your credit balance",
2135
+ className: "text-muted-foreground text-xs",
2136
+ children: formatMoney(effectiveBalance)
2042
2137
  }
2043
2138
  )
2044
2139
  ] }),
2045
- /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center gap-0.5", children: [
2046
- /* @__PURE__ */ jsx4(
2047
- "button",
2048
- {
2049
- ref: historyButtonRef,
2050
- type: "button",
2051
- onClick: openHistory,
2052
- "aria-label": "Chat history",
2053
- "aria-pressed": historyOpen,
2054
- className: `rounded-md p-1.5 transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${historyOpen ? "bg-muted text-foreground" : "text-muted-foreground"}`,
2055
- children: /* @__PURE__ */ jsx4(History, { className: "h-4 w-4" })
2056
- }
2057
- ),
2058
- /* @__PURE__ */ jsx4(
2059
- "button",
2060
- {
2061
- type: "button",
2062
- onClick: chat.reset,
2063
- "aria-label": "New chat",
2064
- title: "New chat",
2065
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2066
- children: /* @__PURE__ */ jsx4(MessageSquarePlus, { className: "h-4 w-4" })
2067
- }
2068
- ),
2069
- /* @__PURE__ */ jsx4(
2070
- "button",
2071
- {
2072
- type: "button",
2073
- onClick: onClose,
2074
- "aria-label": "Close assistant",
2075
- className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2076
- children: /* @__PURE__ */ jsx4(X, { className: "h-4 w-4" })
2077
- }
2078
- )
2079
- ] })
2080
- ] }),
2081
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3 pb-2", children: [
2082
- pickerModels.length > 0 ? /* @__PURE__ */ jsx4(
2083
- ModelPicker,
2140
+ conversationTitle && /* @__PURE__ */ jsx5(
2141
+ "span",
2084
2142
  {
2085
- variant: "pill",
2086
- label: "Model",
2087
- value: pickerValue,
2088
- onChange: (id) => chat.setModel(nextModelSelection(id, models.default)),
2089
- models: pickerModels
2143
+ className: "truncate text-muted-foreground text-xs",
2144
+ title: conversationTitle,
2145
+ children: conversationTitle
2090
2146
  }
2091
- ) : /* @__PURE__ */ jsx4("span", { className: "px-1 text-muted-foreground text-xs", children: "Default model" }),
2092
- /* @__PURE__ */ jsxs2(
2147
+ )
2148
+ ] }),
2149
+ /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-1", children: [
2150
+ /* @__PURE__ */ jsxs3(
2093
2151
  "div",
2094
2152
  {
2095
2153
  className: "flex items-center overflow-hidden rounded-md border border-border",
2096
2154
  role: "group",
2097
2155
  "aria-label": "Text size",
2098
2156
  children: [
2099
- /* @__PURE__ */ jsx4(
2157
+ /* @__PURE__ */ jsx5(
2100
2158
  "button",
2101
2159
  {
2102
2160
  type: "button",
@@ -2104,10 +2162,10 @@ function AssistantPanel({
2104
2162
  disabled: !font.canDecrease,
2105
2163
  "aria-label": "Decrease text size",
2106
2164
  className: "px-1.5 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent",
2107
- children: /* @__PURE__ */ jsx4(Minus, { className: "h-3.5 w-3.5" })
2165
+ children: /* @__PURE__ */ jsx5(Minus, { className: "h-3.5 w-3.5" })
2108
2166
  }
2109
2167
  ),
2110
- /* @__PURE__ */ jsx4(
2168
+ /* @__PURE__ */ jsx5(
2111
2169
  "button",
2112
2170
  {
2113
2171
  type: "button",
@@ -2115,100 +2173,109 @@ function AssistantPanel({
2115
2173
  disabled: !font.canIncrease,
2116
2174
  "aria-label": "Increase text size",
2117
2175
  className: "border-border border-l px-1.5 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:hover:bg-transparent",
2118
- children: /* @__PURE__ */ jsx4(Plus, { className: "h-3.5 w-3.5" })
2176
+ children: /* @__PURE__ */ jsx5(Plus, { className: "h-3.5 w-3.5" })
2119
2177
  }
2120
2178
  )
2121
2179
  ]
2122
2180
  }
2181
+ ),
2182
+ /* @__PURE__ */ jsx5(
2183
+ "button",
2184
+ {
2185
+ ref: historyButtonRef,
2186
+ type: "button",
2187
+ onClick: toggleHistory,
2188
+ "aria-label": "Chat history",
2189
+ "aria-pressed": view === "history",
2190
+ className: `rounded-md p-1.5 transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring ${view === "history" ? "bg-muted text-foreground" : "text-muted-foreground"}`,
2191
+ children: /* @__PURE__ */ jsx5(History, { className: "h-4 w-4" })
2192
+ }
2193
+ ),
2194
+ /* @__PURE__ */ jsx5(
2195
+ "button",
2196
+ {
2197
+ type: "button",
2198
+ onClick: () => {
2199
+ chat.reset();
2200
+ setView("chat");
2201
+ },
2202
+ "aria-label": "New chat",
2203
+ title: "New chat",
2204
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2205
+ children: /* @__PURE__ */ jsx5(MessageSquarePlus, { className: "h-4 w-4" })
2206
+ }
2207
+ ),
2208
+ /* @__PURE__ */ jsx5(
2209
+ "button",
2210
+ {
2211
+ type: "button",
2212
+ onClick: onClose,
2213
+ "aria-label": "Close assistant",
2214
+ className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2215
+ children: /* @__PURE__ */ jsx5(X, { className: "h-4 w-4" })
2216
+ }
2123
2217
  )
2124
- ] }),
2125
- historyOpen && /* @__PURE__ */ jsxs2(
2126
- "div",
2127
- {
2128
- ref: historyRef,
2129
- role: "dialog",
2130
- "aria-label": "Recent conversations",
2131
- tabIndex: -1,
2132
- className: "absolute inset-x-2 top-full z-30 mt-1 overflow-hidden rounded-lg border border-border bg-surface-container-highest shadow-xl ring-1 ring-black/5 focus:outline-none",
2133
- children: [
2134
- /* @__PURE__ */ jsx4("div", { className: "border-border border-b bg-surface-container-high px-3 py-2 font-medium text-[11px] text-muted-foreground uppercase tracking-wide", children: "Recent conversations" }),
2135
- /* @__PURE__ */ jsx4("div", { className: "max-h-[min(18rem,50vh)] overflow-y-auto", children: threads.threads.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "px-3 py-3 text-muted-foreground text-xs", children: threads.loaded ? "No past conversations." : "Loading\u2026" }) : /* @__PURE__ */ jsx4("ul", { className: "py-1", children: threads.threads.map((t) => {
2136
- const busyActive = state.threadId === t.id && state.status !== "idle";
2137
- const active = state.threadId === t.id;
2138
- return /* @__PURE__ */ jsxs2(
2139
- "li",
2140
- {
2141
- className: `group flex items-center transition-colors hover:bg-muted/60 ${active ? "bg-primary/10" : ""}`,
2142
- children: [
2143
- /* @__PURE__ */ jsx4(
2144
- "button",
2145
- {
2146
- type: "button",
2147
- onClick: () => {
2148
- chat.switchThread(t.id);
2149
- setHistoryOpen(false);
2150
- },
2151
- className: `min-w-0 flex-1 truncate px-3 py-2 text-left text-xs ${active ? "font-medium text-foreground" : "text-foreground"}`,
2152
- children: t.title ?? "Untitled conversation"
2153
- }
2154
- ),
2155
- threads.canRemove && /* @__PURE__ */ jsx4(
2156
- "button",
2157
- {
2158
- type: "button",
2159
- onClick: () => void deleteThread(t.id),
2160
- disabled: busyActive,
2161
- "aria-label": "Delete conversation",
2162
- title: busyActive ? "Can't delete while this conversation is active" : "Delete conversation",
2163
- className: "shrink-0 p-2 text-muted-foreground opacity-0 transition hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30",
2164
- children: /* @__PURE__ */ jsx4(Trash2, { className: "h-3.5 w-3.5" })
2165
- }
2166
- )
2167
- ]
2168
- },
2169
- t.id
2170
- );
2171
- }) }) })
2172
- ]
2173
- }
2174
- )
2175
- ] }),
2176
- /* @__PURE__ */ jsx4(
2218
+ ] })
2219
+ ] }) }),
2220
+ /* @__PURE__ */ jsx5(
2177
2221
  "div",
2178
2222
  {
2179
- role: "log",
2223
+ ref: logRef,
2224
+ tabIndex: -1,
2180
2225
  "aria-label": "Conversation",
2181
- "aria-live": "polite",
2182
- className: "min-h-0 flex-1 overflow-y-auto px-2 py-3",
2183
- style: { zoom: font.scale },
2184
- children: renderTranscript ? renderTranscript({
2185
- messages: state.messages,
2186
- reasoning: state.reasoning,
2187
- streamingId: state.streamingId,
2188
- model: state.model,
2189
- isStreaming: streaming,
2190
- isThinking,
2191
- pendingProposals: state.pendingProposals,
2192
- usage: state.usage,
2193
- renderProposal
2194
- }) : /* @__PURE__ */ jsx4(
2195
- AgentTimeline,
2226
+ role: view === "chat" ? "log" : void 0,
2227
+ "aria-live": view === "chat" ? "polite" : void 0,
2228
+ className: "min-h-0 flex-1 overflow-y-auto focus:outline-none",
2229
+ children: view === "history" ? /* @__PURE__ */ jsx5(
2230
+ AssistantHistory,
2196
2231
  {
2197
- items: buildAssistantTimeline(state, renderProposal),
2198
- isThinking,
2199
- emptyState: /* @__PURE__ */ jsx4("p", { className: "px-4 py-8 text-center text-muted-foreground text-sm", children: EMPTY_STATE })
2232
+ threads: threads.threads,
2233
+ loaded: threads.loaded,
2234
+ activeThreadId: state.threadId,
2235
+ activeBusy: state.status !== "idle",
2236
+ canRemove: threads.canRemove,
2237
+ onSelect: (id) => {
2238
+ chat.switchThread(id);
2239
+ setView("chat");
2240
+ },
2241
+ onDelete: (id) => void deleteThread(id)
2200
2242
  }
2243
+ ) : (
2244
+ // The text-size control zooms the transcript only — not the history
2245
+ // view's search box and buttons. `zoom` scales every descendant
2246
+ // uniformly regardless of which renderer draws the conversation; an
2247
+ // inline `font-size` would not (the transcript's text utilities set
2248
+ // absolute rem sizes), and `transform: scale` would break the scroll
2249
+ // container by keeping the original layout box.
2250
+ /* @__PURE__ */ jsx5("div", { className: "px-2 py-3", style: { zoom: font.scale }, children: renderTranscript ? renderTranscript({
2251
+ messages: state.messages,
2252
+ reasoning: state.reasoning,
2253
+ streamingId: state.streamingId,
2254
+ model: state.model,
2255
+ isStreaming: streaming,
2256
+ isThinking,
2257
+ pendingProposals: state.pendingProposals,
2258
+ usage: state.usage,
2259
+ renderProposal
2260
+ }) : /* @__PURE__ */ jsx5(
2261
+ AgentTimeline,
2262
+ {
2263
+ items: buildAssistantTimeline(state, renderProposal),
2264
+ isThinking,
2265
+ emptyState: /* @__PURE__ */ jsx5("p", { className: "px-4 py-8 text-center text-muted-foreground text-sm", children: EMPTY_STATE })
2266
+ }
2267
+ ) })
2201
2268
  )
2202
2269
  }
2203
2270
  ),
2204
- errorView && /* @__PURE__ */ jsxs2(
2271
+ errorView && /* @__PURE__ */ jsxs3(
2205
2272
  "div",
2206
2273
  {
2207
2274
  role: "alert",
2208
2275
  className: "mx-4 mb-2 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-2 text-sm",
2209
2276
  children: [
2210
- /* @__PURE__ */ jsx4("p", { className: "text-foreground", children: errorView.message }),
2211
- errorView.cta && /* @__PURE__ */ jsxs2(
2277
+ /* @__PURE__ */ jsx5("p", { className: "text-foreground", children: errorView.message }),
2278
+ errorView.cta && /* @__PURE__ */ jsxs3(
2212
2279
  "button",
2213
2280
  {
2214
2281
  type: "button",
@@ -2223,14 +2290,14 @@ function AssistantPanel({
2223
2290
  ]
2224
2291
  }
2225
2292
  ),
2226
- low && /* @__PURE__ */ jsxs2(
2293
+ low && /* @__PURE__ */ jsxs3(
2227
2294
  "div",
2228
2295
  {
2229
2296
  role: "status",
2230
2297
  className: "mx-4 mb-2 rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm",
2231
2298
  children: [
2232
- /* @__PURE__ */ jsx4("p", { className: "text-foreground", children: "Your credit balance is running low." }),
2233
- /* @__PURE__ */ jsx4(
2299
+ /* @__PURE__ */ jsx5("p", { className: "text-foreground", children: "Your credit balance is running low." }),
2300
+ /* @__PURE__ */ jsx5(
2234
2301
  "button",
2235
2302
  {
2236
2303
  type: "button",
@@ -2242,12 +2309,25 @@ function AssistantPanel({
2242
2309
  ]
2243
2310
  }
2244
2311
  ),
2245
- /* @__PURE__ */ jsxs2("div", { className: "border-border border-t p-2", children: [
2246
- streaming && /* @__PURE__ */ jsx4("div", { className: "px-2 pb-1.5", "aria-label": "Assistant is working", children: /* @__PURE__ */ jsx4(ThinkingIndicator, {}) }),
2247
- /* @__PURE__ */ jsx4(
2312
+ /* @__PURE__ */ jsxs3("div", { className: "border-border border-t p-2", children: [
2313
+ streaming && /* @__PURE__ */ jsx5("div", { className: "px-2 pb-1.5", "aria-label": "Assistant is working", children: /* @__PURE__ */ jsx5(ThinkingIndicator, {}) }),
2314
+ /* @__PURE__ */ jsx5("div", { className: "mb-1.5 flex items-center px-1", children: pickerModels.length > 0 ? /* @__PURE__ */ jsx5(
2315
+ ModelPicker,
2316
+ {
2317
+ variant: "pill",
2318
+ label: "Model",
2319
+ value: pickerValue,
2320
+ onChange: (id) => chat.setModel(nextModelSelection(id, models.default)),
2321
+ models: pickerModels
2322
+ }
2323
+ ) : /* @__PURE__ */ jsx5("span", { className: "px-1 text-muted-foreground text-xs", children: "Default model" }) }),
2324
+ /* @__PURE__ */ jsx5(
2248
2325
  ChatInput,
2249
2326
  {
2250
- onSend: (message) => chat.send(message),
2327
+ onSend: (message) => {
2328
+ setView("chat");
2329
+ chat.send(message);
2330
+ },
2251
2331
  onCancel: chat.stop,
2252
2332
  isStreaming: streaming,
2253
2333
  disabled: chat.restoring || state.status === "awaiting_confirm",
@@ -2266,27 +2346,27 @@ import {
2266
2346
  createContext as createContext2,
2267
2347
  useCallback as useCallback4,
2268
2348
  useContext as useContext2,
2269
- useMemo as useMemo2,
2270
- useState as useState6
2349
+ useMemo as useMemo3,
2350
+ useState as useState7
2271
2351
  } from "react";
2272
- import { jsx as jsx5 } from "react/jsx-runtime";
2352
+ import { jsx as jsx6 } from "react/jsx-runtime";
2273
2353
  var AssistantLauncherContext = createContext2(null);
2274
2354
  function AssistantLauncherProvider({
2275
2355
  children
2276
2356
  }) {
2277
- const [open, setOpen] = useState6(false);
2278
- const [seed, setSeed] = useState6(null);
2357
+ const [open, setOpen] = useState7(false);
2358
+ const [seed, setSeed] = useState7(null);
2279
2359
  const openAssistant = useCallback4((next) => {
2280
2360
  if (next != null) setSeed(next);
2281
2361
  setOpen(true);
2282
2362
  }, []);
2283
2363
  const closeAssistant = useCallback4(() => setOpen(false), []);
2284
2364
  const clearSeed = useCallback4(() => setSeed(null), []);
2285
- const value = useMemo2(
2365
+ const value = useMemo3(
2286
2366
  () => ({ open, seed, openAssistant, closeAssistant, clearSeed }),
2287
2367
  [open, seed, openAssistant, closeAssistant, clearSeed]
2288
2368
  );
2289
- return /* @__PURE__ */ jsx5(AssistantLauncherContext.Provider, { value, children });
2369
+ return /* @__PURE__ */ jsx6(AssistantLauncherContext.Provider, { value, children });
2290
2370
  }
2291
2371
  function useAssistantLauncher() {
2292
2372
  const ctx = useContext2(AssistantLauncherContext);
@@ -2300,7 +2380,7 @@ function useAssistantLauncher() {
2300
2380
 
2301
2381
  // src/assistant/ResizeHandle.tsx
2302
2382
  import { useRef as useRef5 } from "react";
2303
- import { jsx as jsx6 } from "react/jsx-runtime";
2383
+ import { jsx as jsx7 } from "react/jsx-runtime";
2304
2384
  function ResizeHandle({
2305
2385
  width,
2306
2386
  maxWidth,
@@ -2348,7 +2428,7 @@ function ResizeHandle({
2348
2428
  };
2349
2429
  return (
2350
2430
  // biome-ignore lint/a11y/useSemanticElements: a focusable drag handle is an ARIA window-splitter (role=separator); no native HTML element provides this.
2351
- /* @__PURE__ */ jsx6(
2431
+ /* @__PURE__ */ jsx7(
2352
2432
  "div",
2353
2433
  {
2354
2434
  role: "separator",
@@ -2364,7 +2444,7 @@ function ResizeHandle({
2364
2444
  onPointerCancel: endDrag,
2365
2445
  onKeyDown,
2366
2446
  className: "group absolute inset-y-0 left-0 z-10 flex w-2 -translate-x-1/2 cursor-ew-resize touch-none items-center justify-center focus:outline-none",
2367
- children: /* @__PURE__ */ jsx6(
2447
+ children: /* @__PURE__ */ jsx7(
2368
2448
  "span",
2369
2449
  {
2370
2450
  "aria-hidden": "true",
@@ -2377,7 +2457,7 @@ function ResizeHandle({
2377
2457
  }
2378
2458
 
2379
2459
  // src/assistant/AssistantDock.tsx
2380
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
2460
+ import { Fragment, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
2381
2461
  function focusableWithin(container) {
2382
2462
  const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
2383
2463
  return Array.from(container.querySelectorAll(selector)).filter(
@@ -2429,7 +2509,7 @@ function AssistantDock({
2429
2509
  openAssistant();
2430
2510
  };
2431
2511
  if (!open) {
2432
- return /* @__PURE__ */ jsx7(
2512
+ return /* @__PURE__ */ jsx8(
2433
2513
  "button",
2434
2514
  {
2435
2515
  ref: launcherRef,
@@ -2437,7 +2517,7 @@ function AssistantDock({
2437
2517
  onClick: openDialog,
2438
2518
  "aria-label": "Open assistant",
2439
2519
  className: "fixed right-4 bottom-4 z-40 flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg transition-colors hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-ring",
2440
- children: /* @__PURE__ */ jsx7(MessageSquare, { className: "h-6 w-6" })
2520
+ children: /* @__PURE__ */ jsx8(MessageSquare, { className: "h-6 w-6" })
2441
2521
  }
2442
2522
  );
2443
2523
  }
@@ -2465,8 +2545,8 @@ function AssistantDock({
2465
2545
  first.focus();
2466
2546
  }
2467
2547
  };
2468
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
2469
- /* @__PURE__ */ jsx7(
2548
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
2549
+ /* @__PURE__ */ jsx8(
2470
2550
  "div",
2471
2551
  {
2472
2552
  "aria-hidden": "true",
@@ -2474,7 +2554,7 @@ function AssistantDock({
2474
2554
  onClick: () => closeAssistant()
2475
2555
  }
2476
2556
  ),
2477
- /* @__PURE__ */ jsxs3(
2557
+ /* @__PURE__ */ jsxs4(
2478
2558
  "div",
2479
2559
  {
2480
2560
  ref: dialogRef,
@@ -2486,7 +2566,7 @@ function AssistantDock({
2486
2566
  style: isDesktop ? { width: `${width}px` } : void 0,
2487
2567
  className: "fixed inset-y-0 right-0 z-50 flex w-full flex-col border-border border-l shadow-xl focus:outline-none",
2488
2568
  children: [
2489
- /* @__PURE__ */ jsx7(
2569
+ /* @__PURE__ */ jsx8(
2490
2570
  AssistantPanel,
2491
2571
  {
2492
2572
  chat,
@@ -2500,7 +2580,7 @@ function AssistantDock({
2500
2580
  },
2501
2581
  userId ?? "anon"
2502
2582
  ),
2503
- isDesktop && /* @__PURE__ */ jsx7(
2583
+ isDesktop && /* @__PURE__ */ jsx8(
2504
2584
  ResizeHandle,
2505
2585
  {
2506
2586
  width,
@@ -43,7 +43,7 @@ function pickDetail(cfg, keys) {
43
43
  }
44
44
  return Object.keys(out).length > 0 ? out : void 0;
45
45
  }
46
- function describeAction(action) {
46
+ function describeActionBase(action) {
47
47
  const rec = asRecord(action);
48
48
  const [kind] = Object.keys(rec);
49
49
  const cfg = asRecord(rec[kind]);
@@ -134,6 +134,41 @@ function describeAction(action) {
134
134
  };
135
135
  }
136
136
  }
137
+ var MAX_CONFIG_DEPTH = 100;
138
+ function toJsonSafe(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
139
+ if (value === null) return null;
140
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
141
+ if (typeof value === "string" || typeof value === "boolean") return value;
142
+ if (typeof value !== "object") return void 0;
143
+ if (value instanceof Date) return value.toISOString();
144
+ if (seen.has(value)) return "[Circular]";
145
+ if (depth >= MAX_CONFIG_DEPTH) return "[Max depth exceeded]";
146
+ seen.add(value);
147
+ let out;
148
+ if (Array.isArray(value)) {
149
+ out = value.map((v) => {
150
+ const s = toJsonSafe(v, seen, depth + 1);
151
+ return s === void 0 ? null : s;
152
+ });
153
+ } else {
154
+ const obj = {};
155
+ for (const [k, v] of Object.entries(value)) {
156
+ const s = toJsonSafe(v, seen, depth + 1);
157
+ if (s !== void 0) obj[k] = s;
158
+ }
159
+ out = obj;
160
+ }
161
+ seen.delete(value);
162
+ return out;
163
+ }
164
+ function withConfig(base, cfg) {
165
+ return Object.keys(cfg).length > 0 ? { ...base, config: toJsonSafe(cfg) } : base;
166
+ }
167
+ function describeAction(action) {
168
+ const rec = asRecord(action);
169
+ const [kind] = Object.keys(rec);
170
+ return withConfig(describeActionBase(action), asRecord(rec[kind]));
171
+ }
137
172
  function describeTrigger(on) {
138
173
  const rec = asRecord(on);
139
174
  if (rec.provider_event) {
@@ -146,36 +181,46 @@ function describeTrigger(on) {
146
181
  if (event) subtitle += ` \xB7 ${event}`;
147
182
  if (actions.length > 0) subtitle += ` (${actions.join("/")})`;
148
183
  if (repo) subtitle += ` on ${repo}`;
149
- return {
150
- title: "Trigger",
151
- kind: "provider_event",
152
- subtitle,
153
- provider: connection,
154
- hasBranches: false,
155
- isRoot: true,
156
- tone: "trigger"
157
- };
184
+ return withConfig(
185
+ {
186
+ title: "Trigger",
187
+ kind: "provider_event",
188
+ subtitle,
189
+ provider: connection,
190
+ hasBranches: false,
191
+ isRoot: true,
192
+ tone: "trigger"
193
+ },
194
+ ev
195
+ );
158
196
  }
159
197
  if (rec.schedule) {
160
198
  const sch = asRecord(rec.schedule);
161
199
  const cron = str(sch.cron);
162
200
  const tz = str(sch.timezone);
163
- return {
164
- title: "Schedule",
165
- kind: "schedule",
166
- subtitle: cron ? tz ? `${cron} (${tz})` : cron : void 0,
201
+ return withConfig(
202
+ {
203
+ title: "Schedule",
204
+ kind: "schedule",
205
+ subtitle: cron ? tz ? `${cron} (${tz})` : cron : void 0,
206
+ hasBranches: false,
207
+ isRoot: true,
208
+ tone: "trigger"
209
+ },
210
+ sch
211
+ );
212
+ }
213
+ const [kind] = Object.keys(rec);
214
+ return withConfig(
215
+ {
216
+ title: "Trigger",
217
+ kind: "trigger",
167
218
  hasBranches: false,
168
219
  isRoot: true,
169
220
  tone: "trigger"
170
- };
171
- }
172
- return {
173
- title: "Trigger",
174
- kind: "trigger",
175
- hasBranches: false,
176
- isRoot: true,
177
- tone: "trigger"
178
- };
221
+ },
222
+ asRecord(kind ? rec[kind] : void 0)
223
+ );
179
224
  }
180
225
  function buildWorkflowGraph(yaml, options) {
181
226
  const reserveRunState = options?.reserveRunState ?? false;
package/dist/globals.css CHANGED
@@ -317,9 +317,6 @@
317
317
  .inset-x-0 {
318
318
  inset-inline: calc(var(--spacing) * 0);
319
319
  }
320
- .inset-x-2 {
321
- inset-inline: calc(var(--spacing) * 2);
322
- }
323
320
  .inset-y-0 {
324
321
  inset-block: calc(var(--spacing) * 0);
325
322
  }
@@ -831,9 +828,6 @@
831
828
  .max-h-\[440px\] {
832
829
  max-height: 440px;
833
830
  }
834
- .max-h-\[min\(18rem\,50vh\)\] {
835
- max-height: min(18rem, 50vh);
836
- }
837
831
  .min-h-0 {
838
832
  min-height: calc(var(--spacing) * 0);
839
833
  }
@@ -2767,6 +2761,9 @@
2767
2761
  .pb-2 {
2768
2762
  padding-bottom: calc(var(--spacing) * 2);
2769
2763
  }
2764
+ .pb-2\.5 {
2765
+ padding-bottom: calc(var(--spacing) * 2.5);
2766
+ }
2770
2767
  .pb-3 {
2771
2768
  padding-bottom: calc(var(--spacing) * 3);
2772
2769
  }
@@ -3497,12 +3494,6 @@
3497
3494
  .ring-\[var\(--md3-outline-variant\)\] {
3498
3495
  --tw-ring-color: var(--md3-outline-variant);
3499
3496
  }
3500
- .ring-black\/5 {
3501
- --tw-ring-color: color-mix(in srgb, #000 5%, transparent);
3502
- @supports (color: color-mix(in lab, red, red)) {
3503
- --tw-ring-color: color-mix(in oklab, var(--color-black) 5%, transparent);
3504
- }
3505
- }
3506
3497
  .ring-border {
3507
3498
  --tw-ring-color: var(--color-border);
3508
3499
  }
@@ -5354,6 +5345,11 @@
5354
5345
  }
5355
5346
  }
5356
5347
  }
5348
+ .\[\@media\(hover\:none\)\]\:opacity-100 {
5349
+ @media (hover:none) {
5350
+ opacity: 100%;
5351
+ }
5352
+ }
5357
5353
  }
5358
5354
  :root,
5359
5355
  [data-sandbox-ui],
package/dist/index.js CHANGED
@@ -63,19 +63,6 @@ import {
63
63
  useTotalRunningSessions
64
64
  } from "./chunk-WID73FPH.js";
65
65
  import "./chunk-2BUPSB7O.js";
66
- import {
67
- TOOL_CATEGORY_ICONS,
68
- cn,
69
- copyText,
70
- formatBytes,
71
- formatDuration,
72
- formatUptime,
73
- getToolCategory,
74
- getToolDisplayMetadata,
75
- getToolErrorText,
76
- timeAgo,
77
- truncateText
78
- } from "./chunk-TK46XFLM.js";
79
66
  import {
80
67
  OpenUIArtifactRenderer
81
68
  } from "./chunk-AZ3AWMTM.js";
@@ -105,6 +92,19 @@ import {
105
92
  snapHarnessToModel,
106
93
  snapModelToHarness
107
94
  } from "./chunk-KOUYJ6QP.js";
95
+ import {
96
+ TOOL_CATEGORY_ICONS,
97
+ cn,
98
+ copyText,
99
+ formatBytes,
100
+ formatDuration,
101
+ formatUptime,
102
+ getToolCategory,
103
+ getToolDisplayMetadata,
104
+ getToolErrorText,
105
+ timeAgo,
106
+ truncateText
107
+ } from "./chunk-TK46XFLM.js";
108
108
  import {
109
109
  ExpandedToolDetail,
110
110
  InlineThinkingItem,
package/dist/styles.css CHANGED
@@ -317,9 +317,6 @@
317
317
  .inset-x-0 {
318
318
  inset-inline: calc(var(--spacing) * 0);
319
319
  }
320
- .inset-x-2 {
321
- inset-inline: calc(var(--spacing) * 2);
322
- }
323
320
  .inset-y-0 {
324
321
  inset-block: calc(var(--spacing) * 0);
325
322
  }
@@ -831,9 +828,6 @@
831
828
  .max-h-\[440px\] {
832
829
  max-height: 440px;
833
830
  }
834
- .max-h-\[min\(18rem\,50vh\)\] {
835
- max-height: min(18rem, 50vh);
836
- }
837
831
  .min-h-0 {
838
832
  min-height: calc(var(--spacing) * 0);
839
833
  }
@@ -2767,6 +2761,9 @@
2767
2761
  .pb-2 {
2768
2762
  padding-bottom: calc(var(--spacing) * 2);
2769
2763
  }
2764
+ .pb-2\.5 {
2765
+ padding-bottom: calc(var(--spacing) * 2.5);
2766
+ }
2770
2767
  .pb-3 {
2771
2768
  padding-bottom: calc(var(--spacing) * 3);
2772
2769
  }
@@ -3497,12 +3494,6 @@
3497
3494
  .ring-\[var\(--md3-outline-variant\)\] {
3498
3495
  --tw-ring-color: var(--md3-outline-variant);
3499
3496
  }
3500
- .ring-black\/5 {
3501
- --tw-ring-color: color-mix(in srgb, #000 5%, transparent);
3502
- @supports (color: color-mix(in lab, red, red)) {
3503
- --tw-ring-color: color-mix(in oklab, var(--color-black) 5%, transparent);
3504
- }
3505
- }
3506
3497
  .ring-border {
3507
3498
  --tw-ring-color: var(--color-border);
3508
3499
  }
@@ -5354,6 +5345,11 @@
5354
5345
  }
5355
5346
  }
5356
5347
  }
5348
+ .\[\@media\(hover\:none\)\]\:opacity-100 {
5349
+ @media (hover:none) {
5350
+ opacity: 100%;
5351
+ }
5352
+ }
5357
5353
  }
5358
5354
  :root,
5359
5355
  [data-sandbox-ui],
@@ -58,6 +58,14 @@ interface WfNodeData extends Record<string, unknown> {
58
58
  path?: string;
59
59
  /** Notable config fields for the expand drawer (kept small + stringified). */
60
60
  detail?: Record<string, string>;
61
+ /** The raw, UNTRUNCATED config for this node — the action/trigger config from
62
+ * the definition. The compact card reads {@link detail}; a full-detail view
63
+ * (e.g. a node drawer) reads this to render every field — the complete prompt,
64
+ * all profile/source/input keys — without the card-sized clamp. It is a
65
+ * JSON-safe deep copy of the config (cycles and non-JSON values normalized),
66
+ * so a consumer can serialize or render it freely. Omitted when the node has
67
+ * no config. */
68
+ config?: Record<string, unknown>;
61
69
  /** Connector slug (e.g. `github`) for the provider chip, when one applies. */
62
70
  provider?: string;
63
71
  /** Small corner tag, e.g. "×3" for a parallel fan-out. */
package/dist/workflows.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildWorkflowGraph
3
- } from "./chunk-DEN4GUA7.js";
3
+ } from "./chunk-WEYFEMIN.js";
4
4
  import "./chunk-7JLB7UZ3.js";
5
5
  import "./chunk-U67V476Y.js";
6
6
 
@@ -27,7 +27,7 @@ function retryImport(factory, retries = 2, delayMs = 350) {
27
27
  function lazyWorkflowGraph() {
28
28
  return lazy(
29
29
  () => retryImport(
30
- () => import("./WorkflowGraph-FSS7GW5O.js").then((m) => ({ default: m.WorkflowGraph }))
30
+ () => import("./WorkflowGraph-VMG2ZEAN.js").then((m) => ({ default: m.WorkflowGraph }))
31
31
  )
32
32
  );
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",