@tangle-network/sandbox-ui 0.43.0 → 0.45.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.
@@ -1,3 +1,6 @@
1
+ import {
2
+ ProviderIcon
3
+ } from "./chunk-H7GMPMPB.js";
1
4
  import {
2
5
  buildWorkflowGraph
3
6
  } from "./chunk-SUC6ZJZW.js";
@@ -75,7 +78,18 @@ function WorkflowNode({ data }) {
75
78
  children: d.subtitle
76
79
  }
77
80
  ),
78
- d.provider && /* @__PURE__ */ jsx("span", { className: "mt-1.5 inline-block rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children: providerLabel(d.provider) }),
81
+ d.provider && /* @__PURE__ */ jsxs("span", { className: "mt-1.5 inline-flex items-center gap-1 rounded bg-background-tertiary px-1.5 py-0.5 text-[10px] text-text-muted", children: [
82
+ /* @__PURE__ */ jsx(
83
+ ProviderIcon,
84
+ {
85
+ id: d.provider,
86
+ displayName: providerLabel(d.provider),
87
+ size: 12,
88
+ className: "rounded-[2px]"
89
+ }
90
+ ),
91
+ providerLabel(d.provider)
92
+ ] }),
79
93
  /* @__PURE__ */ jsx(
80
94
  Handle,
81
95
  {
package/dist/assistant.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ ProviderIcon
3
+ } from "./chunk-H7GMPMPB.js";
1
4
  import {
2
5
  providerLabel
3
6
  } from "./chunk-7JLB7UZ3.js";
@@ -1533,8 +1536,8 @@ import {
1533
1536
  } from "react";
1534
1537
 
1535
1538
  // src/assistant/AssistantPanel.tsx
1536
- import { AgentTimeline, ChatInput } from "@tangle-network/ui/chat";
1537
- import { History, Minus, Plus, RotateCcw, Trash2, X } from "lucide-react";
1539
+ import { AgentTimeline, ChatInput, ThinkingIndicator } from "@tangle-network/ui/chat";
1540
+ import { History, MessageSquarePlus, Minus, Plus, Trash2, X } from "lucide-react";
1538
1541
  import { useRef as useRef4, useState as useState5 } from "react";
1539
1542
 
1540
1543
  // src/assistant/build-timeline.tsx
@@ -1670,7 +1673,7 @@ function ProposalCard({
1670
1673
  )
1671
1674
  ] })
1672
1675
  ] }),
1673
- showGraph && tab === "graph" ? /* @__PURE__ */ jsx3("div", { className: "mt-1 h-48 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 }) })
1676
+ 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 }) })
1674
1677
  ] }),
1675
1678
  proposal.requirements && proposal.requirements.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded border border-border p-2", children: [
1676
1679
  /* @__PURE__ */ jsx3("p", { className: "text-muted-foreground text-xs", children: "Integrations" }),
@@ -1737,16 +1740,33 @@ function RequirementRow({
1737
1740
  const canConnect = !req.connected && req.connectUrl !== null;
1738
1741
  const target = req.connectUrl ?? "/app/integrations";
1739
1742
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2 text-xs", children: [
1740
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1743
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
1741
1744
  /* @__PURE__ */ jsx3(
1742
- "span",
1745
+ ProviderIcon,
1743
1746
  {
1744
- "aria-hidden": "true",
1745
- className: `h-2 w-2 rounded-full ${req.connected ? "bg-primary" : "border border-muted-foreground"}`
1747
+ id: req.provider,
1748
+ displayName: label,
1749
+ size: 16,
1750
+ className: "rounded"
1746
1751
  }
1747
1752
  ),
1748
- /* @__PURE__ */ jsx3("span", { className: "text-foreground", children: kindLabel }),
1749
- /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground", children: statusText })
1753
+ /* @__PURE__ */ jsx3("span", { className: "truncate text-foreground", children: kindLabel }),
1754
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
1755
+ /* @__PURE__ */ jsx3(
1756
+ "span",
1757
+ {
1758
+ "aria-hidden": "true",
1759
+ className: `h-1.5 w-1.5 rounded-full ${req.connected ? "bg-primary" : "border border-muted-foreground"}`
1760
+ }
1761
+ ),
1762
+ /* @__PURE__ */ jsx3(
1763
+ "span",
1764
+ {
1765
+ className: req.connected ? "text-primary" : "text-muted-foreground",
1766
+ children: statusText
1767
+ }
1768
+ )
1769
+ ] })
1750
1770
  ] }),
1751
1771
  canConnect && /* @__PURE__ */ jsxs(
1752
1772
  "button",
@@ -1910,6 +1930,9 @@ function AssistantPanel({
1910
1930
  const errorView = state.error ? presentError(state.error.code, state.error.message) : null;
1911
1931
  const low = isLowBalance(effectiveBalance) && !errorView;
1912
1932
  const streaming = state.status === "streaming";
1933
+ const firstUserText = state.messages.find((m) => m.role === "user")?.text.trim();
1934
+ const titleChars = firstUserText ? Array.from(firstUserText) : [];
1935
+ const conversationTitle = firstUserText ? titleChars.length > 60 ? `${titleChars.slice(0, 60).join("")}\u2026` : firstUserText : null;
1913
1936
  const renderProposal = (proposal) => /* @__PURE__ */ jsx4(
1914
1937
  ProposalCard,
1915
1938
  {
@@ -1942,19 +1965,29 @@ function AssistantPanel({
1942
1965
  }
1943
1966
  };
1944
1967
  return /* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col bg-background", children: [
1945
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between border-border border-b px-4 py-3", children: [
1946
- /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-2", children: [
1947
- /* @__PURE__ */ jsx4("span", { className: "font-medium text-foreground text-sm", children: "Assistant" }),
1948
- /* @__PURE__ */ jsx4(
1968
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 border-border border-b px-4 py-3", children: [
1969
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-col", children: [
1970
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-baseline gap-2", children: [
1971
+ /* @__PURE__ */ jsx4("span", { className: "font-medium text-foreground text-sm", children: "Assistant" }),
1972
+ /* @__PURE__ */ jsx4(
1973
+ "span",
1974
+ {
1975
+ "aria-label": "Your credit balance",
1976
+ className: "text-muted-foreground text-xs",
1977
+ children: formatMoney(effectiveBalance)
1978
+ }
1979
+ )
1980
+ ] }),
1981
+ conversationTitle && /* @__PURE__ */ jsx4(
1949
1982
  "span",
1950
1983
  {
1951
- "aria-label": "Your credit balance",
1952
- className: "text-muted-foreground text-xs",
1953
- children: formatMoney(effectiveBalance)
1984
+ className: "truncate text-muted-foreground text-xs",
1985
+ title: conversationTitle,
1986
+ children: conversationTitle
1954
1987
  }
1955
1988
  )
1956
1989
  ] }),
1957
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1", children: [
1990
+ /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center gap-1", children: [
1958
1991
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center", role: "group", "aria-label": "Text size", children: [
1959
1992
  /* @__PURE__ */ jsx4(
1960
1993
  "button",
@@ -1996,8 +2029,9 @@ function AssistantPanel({
1996
2029
  type: "button",
1997
2030
  onClick: chat.reset,
1998
2031
  "aria-label": "New chat",
2032
+ title: "New chat",
1999
2033
  className: "rounded p-1 text-muted-foreground hover:text-foreground",
2000
- children: /* @__PURE__ */ jsx4(RotateCcw, { className: "h-4 w-4" })
2034
+ children: /* @__PURE__ */ jsx4(MessageSquarePlus, { className: "h-4 w-4" })
2001
2035
  }
2002
2036
  ),
2003
2037
  /* @__PURE__ */ jsx4(
@@ -2133,19 +2167,22 @@ function AssistantPanel({
2133
2167
  ]
2134
2168
  }
2135
2169
  ),
2136
- /* @__PURE__ */ jsx4("div", { className: "border-border border-t p-2", children: /* @__PURE__ */ jsx4(
2137
- ChatInput,
2138
- {
2139
- onSend: (message) => chat.send(message),
2140
- onCancel: chat.stop,
2141
- isStreaming: streaming,
2142
- disabled: chat.restoring || state.status === "awaiting_confirm",
2143
- placeholder: "Message the assistant\u2026",
2144
- inputLabel: null,
2145
- idleStatus: null,
2146
- streamingStatus: null
2147
- }
2148
- ) })
2170
+ /* @__PURE__ */ jsxs2("div", { className: "border-border border-t p-2", children: [
2171
+ streaming && /* @__PURE__ */ jsx4("div", { className: "px-2 pb-1.5", "aria-label": "Assistant is working", children: /* @__PURE__ */ jsx4(ThinkingIndicator, {}) }),
2172
+ /* @__PURE__ */ jsx4(
2173
+ ChatInput,
2174
+ {
2175
+ onSend: (message) => chat.send(message),
2176
+ onCancel: chat.stop,
2177
+ isStreaming: streaming,
2178
+ disabled: chat.restoring || state.status === "awaiting_confirm",
2179
+ placeholder: "Message the assistant\u2026",
2180
+ inputLabel: null,
2181
+ idleStatus: null,
2182
+ streamingStatus: null
2183
+ }
2184
+ )
2185
+ ] })
2149
2186
  ] });
2150
2187
  }
2151
2188
 
@@ -0,0 +1,191 @@
1
+ // src/integrations/provider-logo.tsx
2
+ import * as React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ function normalizeProviderId(id) {
5
+ return id.toLowerCase().replace(/[_\s]+/g, "-").replace(/-(business|oauth|api|app|mail|sms|pack|connector|v\d+)$/g, "");
6
+ }
7
+ var PROVIDER_LOGO_SLUGS = {
8
+ gmail: "gmail",
9
+ googlemail: "gmail",
10
+ google: "google",
11
+ "google-drive": "googledrive",
12
+ "google-calendar": "googlecalendar",
13
+ "google-sheets": "googlesheets",
14
+ "google-docs": "googledocs",
15
+ "google-meet": "googlemeet",
16
+ "google-forms": "googleforms",
17
+ "google-ads": "googleads",
18
+ "google-analytics": "googleanalytics",
19
+ outlook: "microsoftoutlook",
20
+ "outlook-mail": "microsoftoutlook",
21
+ "microsoft-outlook": "microsoftoutlook",
22
+ "microsoft-calendar": "microsoftoutlook",
23
+ "microsoft-teams": "microsoftteams",
24
+ teams: "microsoftteams",
25
+ "microsoft-excel": "microsoftexcel",
26
+ excel: "microsoftexcel",
27
+ onedrive: "microsoftonedrive",
28
+ sharepoint: "microsoftsharepoint",
29
+ twitter: "x",
30
+ x: "x",
31
+ meta: "meta",
32
+ "stripe-pack": "stripe",
33
+ "twilio-sms": "twilio",
34
+ webhook: "webhooks",
35
+ webhooks: "webhooks",
36
+ hubspot: "hubspot",
37
+ salesforce: "salesforce",
38
+ pipedrive: "pipedrive",
39
+ zoho: "zoho",
40
+ quickbooks: "quickbooks",
41
+ intercom: "intercom",
42
+ zendesk: "zendesk",
43
+ freshdesk: "freshdesk",
44
+ monday: "mondaydotcom",
45
+ "monday-com": "mondaydotcom",
46
+ clickup: "clickup",
47
+ basecamp: "basecamp",
48
+ todoist: "todoist",
49
+ calendly: "calendly",
50
+ typeform: "typeform",
51
+ surveymonkey: "surveymonkey",
52
+ klaviyo: "klaviyo",
53
+ sendinblue: "brevo",
54
+ brevo: "brevo",
55
+ "constant-contact": "constantcontact",
56
+ "active-campaign": "activecampaign",
57
+ activecampaign: "activecampaign",
58
+ "google-chat": "googlechat",
59
+ whatsapp: "whatsapp",
60
+ telegram: "telegram",
61
+ bigquery: "googlebigquery",
62
+ snowflake: "snowflake",
63
+ postgres: "postgresql",
64
+ postgresql: "postgresql",
65
+ mysql: "mysql",
66
+ mongodb: "mongodb",
67
+ redis: "redis",
68
+ supabase: "supabase",
69
+ firebase: "firebase",
70
+ "aws-s3": "amazons3",
71
+ s3: "amazons3",
72
+ woocommerce: "woocommerce",
73
+ bigcommerce: "bigcommerce",
74
+ squarespace: "squarespace",
75
+ wix: "wix",
76
+ webflow: "webflow",
77
+ wordpress: "wordpress",
78
+ contentful: "contentful",
79
+ sanity: "sanity",
80
+ figma: "figma",
81
+ miro: "miro",
82
+ confluence: "confluence",
83
+ bitbucket: "bitbucket",
84
+ pagerduty: "pagerduty",
85
+ datadog: "datadog",
86
+ sentry: "sentry",
87
+ segment: "segment",
88
+ amplitude: "amplitude",
89
+ mixpanel: "mixpanel",
90
+ posthog: "posthog",
91
+ facebook: "facebook",
92
+ instagram: "instagram",
93
+ tiktok: "tiktok",
94
+ youtube: "youtube",
95
+ reddit: "reddit",
96
+ pinterest: "pinterest",
97
+ buffer: "buffer",
98
+ hootsuite: "hootsuite"
99
+ };
100
+ function providerLogoCandidates(opts) {
101
+ const out = [];
102
+ if (opts.iconUrl) out.push(opts.iconUrl);
103
+ const raw = opts.id.toLowerCase();
104
+ const norm = normalizeProviderId(raw);
105
+ const slugs = /* @__PURE__ */ new Set();
106
+ const curated = PROVIDER_LOGO_SLUGS[raw] ?? PROVIDER_LOGO_SLUGS[norm];
107
+ if (curated) slugs.add(curated);
108
+ slugs.add(norm.replace(/-/g, ""));
109
+ slugs.add(raw.replace(/[-_\s]/g, ""));
110
+ for (const slug of slugs) {
111
+ if (slug) out.push(`https://cdn.simpleicons.org/${slug}`);
112
+ }
113
+ return out;
114
+ }
115
+ function monogramColor(seed) {
116
+ const palette = [
117
+ "#6366f1",
118
+ "#8b5cf6",
119
+ "#ec4899",
120
+ "#f43f5e",
121
+ "#f97316",
122
+ "#eab308",
123
+ "#22c55e",
124
+ "#14b8a6",
125
+ "#0ea5e9",
126
+ "#3b82f6"
127
+ ];
128
+ let hash = 0;
129
+ for (let i = 0; i < seed.length; i += 1) {
130
+ hash = hash * 31 + seed.charCodeAt(i) >>> 0;
131
+ }
132
+ return palette[hash % palette.length];
133
+ }
134
+ function ProviderIcon({
135
+ id,
136
+ iconUrl,
137
+ displayName,
138
+ size = 24,
139
+ className
140
+ }) {
141
+ const candidates = React.useMemo(
142
+ () => providerLogoCandidates({ id, iconUrl }),
143
+ [id, iconUrl]
144
+ );
145
+ const [index, setIndex] = React.useState(0);
146
+ const candidateKey = `${id} ${iconUrl ?? ""}`;
147
+ const [seenKey, setSeenKey] = React.useState(candidateKey);
148
+ if (candidateKey !== seenKey) {
149
+ setSeenKey(candidateKey);
150
+ setIndex(0);
151
+ }
152
+ const label = (displayName ?? id).trim();
153
+ const initial = label.charAt(0).toUpperCase() || "?";
154
+ const src = candidates[index];
155
+ if (!src) {
156
+ return /* @__PURE__ */ jsx(
157
+ "span",
158
+ {
159
+ className: `flex shrink-0 items-center justify-center font-semibold text-white ${className ?? ""}`,
160
+ style: {
161
+ width: size,
162
+ height: size,
163
+ backgroundColor: monogramColor(id),
164
+ fontSize: Math.round(size * 0.42)
165
+ },
166
+ "aria-hidden": true,
167
+ children: initial
168
+ }
169
+ );
170
+ }
171
+ return /* @__PURE__ */ jsx(
172
+ "img",
173
+ {
174
+ src,
175
+ alt: "",
176
+ width: size,
177
+ height: size,
178
+ loading: "lazy",
179
+ className: `shrink-0 object-contain ${className ?? ""}`,
180
+ style: { width: size, height: size },
181
+ onError: () => setIndex((i) => i + 1)
182
+ }
183
+ );
184
+ }
185
+
186
+ export {
187
+ normalizeProviderId,
188
+ providerLogoCandidates,
189
+ monogramColor,
190
+ ProviderIcon
191
+ };
package/dist/globals.css CHANGED
@@ -692,6 +692,9 @@
692
692
  .h-56 {
693
693
  height: calc(var(--spacing) * 56);
694
694
  }
695
+ .h-64 {
696
+ height: calc(var(--spacing) * 64);
697
+ }
695
698
  .h-\[3px\] {
696
699
  height: 3px;
697
700
  }
@@ -2378,6 +2381,9 @@
2378
2381
  .pb-1 {
2379
2382
  padding-bottom: calc(var(--spacing) * 1);
2380
2383
  }
2384
+ .pb-1\.5 {
2385
+ padding-bottom: calc(var(--spacing) * 1.5);
2386
+ }
2381
2387
  .pb-2 {
2382
2388
  padding-bottom: calc(var(--spacing) * 2);
2383
2389
  }
@@ -4163,7 +4169,9 @@
4163
4169
  }
4164
4170
  }
4165
4171
  :root,
4166
- [data-sandbox-ui] {
4172
+ [data-sandbox-ui],
4173
+ [data-theme="dark"],
4174
+ .dark {
4167
4175
  --md3-surface: #0a0a14;
4168
4176
  --md3-surface-dim: #07070d;
4169
4177
  --md3-surface-bright: #1b1a2c;
@@ -4406,7 +4414,9 @@
4406
4414
  --font-display: "Inter", "Geist", sans-serif;
4407
4415
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
4408
4416
  }
4409
- [data-sandbox-theme="vault"] {
4417
+ [data-sandbox-theme="vault"],
4418
+ [data-theme="light"],
4419
+ .light {
4410
4420
  --md3-surface: #f8f9fb;
4411
4421
  --md3-surface-dim: #d9dae0;
4412
4422
  --md3-surface-bright: #f8f9fb;
@@ -1,3 +1,9 @@
1
+ import {
2
+ monogramColor,
3
+ normalizeProviderId,
4
+ providerLogoCandidates
5
+ } from "./chunk-H7GMPMPB.js";
6
+
1
7
  // src/integrations/integrations-panel.tsx
2
8
  import * as React from "react";
3
9
  import { Card, CardContent, EmptyState } from "@tangle-network/ui/primitives";
@@ -42,141 +48,17 @@ var DEFAULT_FEATURED_IDS = [
42
48
  function defaultConnectorOf(provider) {
43
49
  return provider.connectors?.[0]?.connectorId ?? provider.providerId;
44
50
  }
45
- function normalizeProviderId(id) {
46
- return id.toLowerCase().replace(/[_\s]+/g, "-").replace(/-(business|oauth|api|app|mail|sms|pack|connector|v\d+)$/g, "");
47
- }
48
- var PROVIDER_LOGO_SLUGS = {
49
- gmail: "gmail",
50
- googlemail: "gmail",
51
- google: "google",
52
- "google-drive": "googledrive",
53
- "google-calendar": "googlecalendar",
54
- "google-sheets": "googlesheets",
55
- "google-docs": "googledocs",
56
- "google-meet": "googlemeet",
57
- "google-forms": "googleforms",
58
- "google-ads": "googleads",
59
- "google-analytics": "googleanalytics",
60
- outlook: "microsoftoutlook",
61
- "outlook-mail": "microsoftoutlook",
62
- "microsoft-outlook": "microsoftoutlook",
63
- "microsoft-calendar": "microsoftoutlook",
64
- "microsoft-teams": "microsoftteams",
65
- teams: "microsoftteams",
66
- "microsoft-excel": "microsoftexcel",
67
- excel: "microsoftexcel",
68
- onedrive: "microsoftonedrive",
69
- sharepoint: "microsoftsharepoint",
70
- twitter: "x",
71
- x: "x",
72
- meta: "meta",
73
- "stripe-pack": "stripe",
74
- "twilio-sms": "twilio",
75
- webhook: "webhooks",
76
- webhooks: "webhooks",
77
- hubspot: "hubspot",
78
- salesforce: "salesforce",
79
- pipedrive: "pipedrive",
80
- zoho: "zoho",
81
- quickbooks: "quickbooks",
82
- intercom: "intercom",
83
- zendesk: "zendesk",
84
- freshdesk: "freshdesk",
85
- monday: "mondaydotcom",
86
- "monday-com": "mondaydotcom",
87
- clickup: "clickup",
88
- basecamp: "basecamp",
89
- todoist: "todoist",
90
- calendly: "calendly",
91
- typeform: "typeform",
92
- surveymonkey: "surveymonkey",
93
- klaviyo: "klaviyo",
94
- sendinblue: "brevo",
95
- brevo: "brevo",
96
- "constant-contact": "constantcontact",
97
- "active-campaign": "activecampaign",
98
- activecampaign: "activecampaign",
99
- "google-chat": "googlechat",
100
- whatsapp: "whatsapp",
101
- telegram: "telegram",
102
- bigquery: "googlebigquery",
103
- snowflake: "snowflake",
104
- postgres: "postgresql",
105
- postgresql: "postgresql",
106
- mysql: "mysql",
107
- mongodb: "mongodb",
108
- redis: "redis",
109
- supabase: "supabase",
110
- firebase: "firebase",
111
- "aws-s3": "amazons3",
112
- s3: "amazons3",
113
- woocommerce: "woocommerce",
114
- bigcommerce: "bigcommerce",
115
- squarespace: "squarespace",
116
- wix: "wix",
117
- webflow: "webflow",
118
- wordpress: "wordpress",
119
- contentful: "contentful",
120
- sanity: "sanity",
121
- figma: "figma",
122
- miro: "miro",
123
- confluence: "confluence",
124
- bitbucket: "bitbucket",
125
- pagerduty: "pagerduty",
126
- datadog: "datadog",
127
- sentry: "sentry",
128
- segment: "segment",
129
- amplitude: "amplitude",
130
- mixpanel: "mixpanel",
131
- posthog: "posthog",
132
- facebook: "facebook",
133
- instagram: "instagram",
134
- tiktok: "tiktok",
135
- youtube: "youtube",
136
- reddit: "reddit",
137
- pinterest: "pinterest",
138
- buffer: "buffer",
139
- hootsuite: "hootsuite"
140
- };
141
- function logoCandidates(provider) {
142
- const out = [];
143
- if (provider.iconUrl) out.push(provider.iconUrl);
144
- const raw = provider.providerId.toLowerCase();
145
- const norm = normalizeProviderId(raw);
146
- const slugs = /* @__PURE__ */ new Set();
147
- const curated = PROVIDER_LOGO_SLUGS[raw] ?? PROVIDER_LOGO_SLUGS[norm];
148
- if (curated) slugs.add(curated);
149
- slugs.add(norm.replace(/-/g, ""));
150
- slugs.add(raw.replace(/[-_\s]/g, ""));
151
- for (const slug of slugs) {
152
- if (slug) out.push(`https://cdn.simpleicons.org/${slug}`);
153
- }
154
- return out;
155
- }
156
- function monogramColor(seed) {
157
- const palette = [
158
- "#6366f1",
159
- "#8b5cf6",
160
- "#ec4899",
161
- "#f43f5e",
162
- "#f97316",
163
- "#eab308",
164
- "#22c55e",
165
- "#14b8a6",
166
- "#0ea5e9",
167
- "#3b82f6"
168
- ];
169
- let hash = 0;
170
- for (let i = 0; i < seed.length; i += 1) {
171
- hash = hash * 31 + seed.charCodeAt(i) >>> 0;
172
- }
173
- return palette[hash % palette.length];
174
- }
175
51
  function ProviderLogo({
176
52
  provider,
177
53
  size = 56
178
54
  }) {
179
- const candidates = React.useMemo(() => logoCandidates(provider), [provider]);
55
+ const candidates = React.useMemo(
56
+ () => providerLogoCandidates({
57
+ id: provider.providerId,
58
+ iconUrl: provider.iconUrl
59
+ }),
60
+ [provider]
61
+ );
180
62
  const [index, setIndex] = React.useState(0);
181
63
  const label = (provider.displayName ?? provider.providerId).trim();
182
64
  const initial = label.charAt(0).toUpperCase() || "?";
package/dist/styles.css CHANGED
@@ -692,6 +692,9 @@
692
692
  .h-56 {
693
693
  height: calc(var(--spacing) * 56);
694
694
  }
695
+ .h-64 {
696
+ height: calc(var(--spacing) * 64);
697
+ }
695
698
  .h-\[3px\] {
696
699
  height: 3px;
697
700
  }
@@ -2378,6 +2381,9 @@
2378
2381
  .pb-1 {
2379
2382
  padding-bottom: calc(var(--spacing) * 1);
2380
2383
  }
2384
+ .pb-1\.5 {
2385
+ padding-bottom: calc(var(--spacing) * 1.5);
2386
+ }
2381
2387
  .pb-2 {
2382
2388
  padding-bottom: calc(var(--spacing) * 2);
2383
2389
  }
@@ -4163,7 +4169,9 @@
4163
4169
  }
4164
4170
  }
4165
4171
  :root,
4166
- [data-sandbox-ui] {
4172
+ [data-sandbox-ui],
4173
+ [data-theme="dark"],
4174
+ .dark {
4167
4175
  --md3-surface: #0a0a14;
4168
4176
  --md3-surface-dim: #07070d;
4169
4177
  --md3-surface-bright: #1b1a2c;
@@ -4406,7 +4414,9 @@
4406
4414
  --font-display: "Inter", "Geist", sans-serif;
4407
4415
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
4408
4416
  }
4409
- [data-sandbox-theme="vault"] {
4417
+ [data-sandbox-theme="vault"],
4418
+ [data-theme="light"],
4419
+ .light {
4410
4420
  --md3-surface: #f8f9fb;
4411
4421
  --md3-surface-dim: #d9dae0;
4412
4422
  --md3-surface-bright: #f8f9fb;
package/dist/tokens.css CHANGED
@@ -2,8 +2,13 @@
2
2
  /* Brand: warm purple palette, #5B4ED4 primary */
3
3
 
4
4
  /* MD3 surface hierarchy */
5
+ /* Dark is the default theme (`:root`/`[data-sandbox-ui]`). It also responds to
6
+ * the standard `[data-theme="dark"]` / `.dark` switch so any consumer can pin or
7
+ * toggle a theme without inventing an ad-hoc `data-sandbox-theme` name. */
5
8
  :root,
6
- [data-sandbox-ui] {
9
+ [data-sandbox-ui],
10
+ [data-theme="dark"],
11
+ .dark {
7
12
  --md3-surface: #0a0a14;
8
13
  --md3-surface-dim: #07070d;
9
14
  --md3-surface-bright: #1b1a2c;
@@ -304,7 +309,13 @@
304
309
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
305
310
  }
306
311
 
307
- [data-sandbox-theme="vault"] {
312
+ /* Light theme — reachable via the named `vault` theme or the standard
313
+ * `[data-theme="light"]` / `.light` switch. Both blocks are single-attribute/
314
+ * class selectors, and this one is later in source order, so when an element
315
+ * carries both `data-sandbox-ui` (dark) and `data-theme="light"`, light wins. */
316
+ [data-sandbox-theme="vault"],
317
+ [data-theme="light"],
318
+ .light {
308
319
  /* Light theme — clean professional, warm purple accents */
309
320
 
310
321
  /* MD3 surface hierarchy */
package/dist/workflows.js CHANGED
@@ -4,11 +4,32 @@ import {
4
4
  import "./chunk-7JLB7UZ3.js";
5
5
 
6
6
  // src/workflows/WorkflowGraphLazy.tsx
7
- import { Component, lazy, Suspense } from "react";
7
+ import {
8
+ Component,
9
+ lazy,
10
+ Suspense,
11
+ useMemo,
12
+ useState
13
+ } from "react";
8
14
  import { jsx, jsxs } from "react/jsx-runtime";
9
- var WorkflowGraphImpl = lazy(
10
- () => import("./WorkflowGraph-5QO6WCY2.js").then((m) => ({ default: m.WorkflowGraph }))
11
- );
15
+ function retryImport(factory, retries = 2, delayMs = 350) {
16
+ return factory().catch((err) => {
17
+ if (retries <= 0) throw err;
18
+ return new Promise((resolve, reject) => {
19
+ setTimeout(
20
+ () => retryImport(factory, retries - 1, delayMs).then(resolve, reject),
21
+ delayMs
22
+ );
23
+ });
24
+ });
25
+ }
26
+ function lazyWorkflowGraph() {
27
+ return lazy(
28
+ () => retryImport(
29
+ () => import("./WorkflowGraph-7YYGIBNV.js").then((m) => ({ default: m.WorkflowGraph }))
30
+ )
31
+ );
32
+ }
12
33
  var GraphErrorBoundary = class extends Component {
13
34
  state = { hasError: false };
14
35
  static getDerivedStateFromError() {
@@ -18,27 +39,45 @@ var GraphErrorBoundary = class extends Component {
18
39
  return this.state.hasError ? this.props.fallback : this.props.children;
19
40
  }
20
41
  };
21
- function GraphFallback({ yaml, className }) {
42
+ function GraphFallback({
43
+ yaml,
44
+ className,
45
+ onRetry
46
+ }) {
22
47
  return /* @__PURE__ */ jsxs(
23
48
  "div",
24
49
  {
25
50
  className: `overflow-auto rounded-lg border border-border bg-background p-2 ${className ?? ""}`,
26
51
  children: [
27
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-text-muted text-xs", children: "Couldn't render the graph \u2014 showing the definition." }),
52
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [
53
+ /* @__PURE__ */ jsx("p", { className: "text-text-muted text-xs", children: "Couldn't render the graph \u2014 showing the definition." }),
54
+ /* @__PURE__ */ jsx(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ onClick: onRetry,
59
+ className: "shrink-0 rounded border border-border px-1.5 py-0.5 text-text-muted text-xs transition hover:text-text",
60
+ children: "Retry"
61
+ }
62
+ )
63
+ ] }),
28
64
  /* @__PURE__ */ jsx("pre", { className: "text-text text-xs", children: /* @__PURE__ */ jsx("code", { children: yaml }) })
29
65
  ]
30
66
  }
31
67
  );
32
68
  }
33
69
  function WorkflowGraph(props) {
70
+ const [attempt, setAttempt] = useState(0);
71
+ const Impl = useMemo(() => lazyWorkflowGraph(), [attempt]);
34
72
  return (
35
- // Key the boundary by the workflow so a different/edited definition remounts
36
- // it and re-attempts the graph — a transient chunk/render failure on one
37
- // workflow doesn't permanently pin the YAML fallback for the next.
73
+ // Key the boundary by the workflow AND the attempt so a different/edited
74
+ // definition or a manual retry remounts it and re-attempts the graph — a
75
+ // transient chunk/render failure on one workflow doesn't permanently pin the
76
+ // YAML fallback.
38
77
  /* @__PURE__ */ jsx(
39
78
  GraphErrorBoundary,
40
79
  {
41
- fallback: /* @__PURE__ */ jsx(GraphFallback, { ...props }),
80
+ fallback: /* @__PURE__ */ jsx(GraphFallback, { ...props, onRetry: () => setAttempt((a) => a + 1) }),
42
81
  children: /* @__PURE__ */ jsx(
43
82
  Suspense,
44
83
  {
@@ -49,11 +88,11 @@ function WorkflowGraph(props) {
49
88
  children: "Loading graph\u2026"
50
89
  }
51
90
  ),
52
- children: /* @__PURE__ */ jsx(WorkflowGraphImpl, { ...props })
91
+ children: /* @__PURE__ */ jsx(Impl, { ...props })
53
92
  }
54
93
  )
55
94
  },
56
- props.yaml
95
+ `${props.yaml}::${attempt}`
57
96
  )
58
97
  );
59
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.43.0",
3
+ "version": "0.45.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",
@@ -122,7 +122,7 @@
122
122
  "test:bridge": "pnpm build && vitest run src/__tests__/re-export-identity.test.ts"
123
123
  },
124
124
  "peerDependencies": {
125
- "@tangle-network/brand": "^0.5.0",
125
+ "@tangle-network/brand": "^0.6.0",
126
126
  "@tangle-network/ui": "^4.0.0",
127
127
  "react": "^18 || ^19",
128
128
  "react-dom": "^18 || ^19",
@@ -176,7 +176,7 @@
176
176
  "@storybook/react-vite": "^8.6.18",
177
177
  "@tailwindcss/postcss": "^4.2.2",
178
178
  "@tailwindcss/vite": "^4.2.2",
179
- "@tangle-network/brand": "^0.5.0",
179
+ "@tangle-network/brand": "^0.6.0",
180
180
  "@tangle-network/ui": "^4.1.0",
181
181
  "@tanstack/react-query": "^5.91.0",
182
182
  "@testing-library/dom": "^10.4.1",