@volter-ai-dev/supercode-ui 0.1.13 → 0.1.14

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {
@@ -24,6 +24,10 @@
24
24
  "types": "./logo.d.ts",
25
25
  "import": "./logo.mjs"
26
26
  },
27
+ "./preact/icon": {
28
+ "types": "./icon.d.ts",
29
+ "import": "./icon.mjs"
30
+ },
27
31
  "./preact/conversation": {
28
32
  "types": "./conversation.d.ts",
29
33
  "import": "./conversation.mjs"
@@ -56,6 +60,8 @@
56
60
  "components.d.ts",
57
61
  "logo.mjs",
58
62
  "logo.d.ts",
63
+ "icon.mjs",
64
+ "icon.d.ts",
59
65
  "conversation.mjs",
60
66
  "conversation.d.ts",
61
67
  "sessions.mjs",
@@ -72,7 +78,7 @@
72
78
  "README.md"
73
79
  ],
74
80
  "scripts": {
75
- "build": "esbuild src/components.jsx src/embed.jsx src/logo.jsx src/conversation.jsx src/sessions.jsx src/composer.jsx src/messenger.jsx --bundle --format=esm --platform=browser --jsx=automatic --jsx-import-source=preact --external:preact --external:preact/* --external:markdown-it --outdir=. --out-extension:.js=.mjs",
81
+ "build": "esbuild src/components.jsx src/embed.jsx src/logo.jsx src/icon.jsx src/conversation.jsx src/sessions.jsx src/composer.jsx src/messenger.jsx --bundle --format=esm --platform=browser --jsx=automatic --jsx-import-source=preact --external:preact --external:preact/* --external:markdown-it --outdir=. --out-extension:.js=.mjs",
76
82
  "build:fixture": "esbuild test/browser-entry.jsx --bundle --format=esm --platform=browser --jsx=automatic --jsx-import-source=preact --outfile=test/browser-bundle.mjs",
77
83
  "storybook": "npm run build && storybook dev -p 6006",
78
84
  "build:storybook": "npm run build && storybook build",
package/sessions.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/sessions.jsx
2
- import { useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "preact/hooks";
2
+ import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState2 } from "preact/hooks";
3
3
 
4
4
  // core.mjs
5
5
  var HARNESS_NAMES = Object.freeze({
@@ -82,13 +82,52 @@ function filterSessions(rows, query) {
82
82
  }
83
83
 
84
84
  // src/conversation.jsx
85
- import { useEffect, useId, useLayoutEffect, useRef, useState } from "preact/hooks";
85
+ import { Fragment as Fragment2 } from "preact";
86
+ import { useEffect as useEffect2, useId, useLayoutEffect, useRef as useRef2, useState } from "preact/hooks";
86
87
 
87
88
  // src/markdown.jsx
88
89
  import MarkdownIt from "markdown-it";
89
- import { useMemo } from "preact/hooks";
90
+ import { useEffect, useMemo, useRef } from "preact/hooks";
90
91
  import { jsx } from "preact/jsx-runtime";
91
92
  var markdown = new MarkdownIt({ html: false, linkify: true, breaks: false });
93
+ var LANGUAGE_LABELS = {
94
+ bash: "Shell",
95
+ css: "CSS",
96
+ html: "HTML",
97
+ js: "JavaScript",
98
+ javascript: "JavaScript",
99
+ jsx: "JSX",
100
+ md: "Markdown",
101
+ markdown: "Markdown",
102
+ py: "Python",
103
+ python: "Python",
104
+ rs: "Rust",
105
+ rust: "Rust",
106
+ sh: "Shell",
107
+ shell: "Shell",
108
+ ts: "TypeScript",
109
+ tsx: "TSX",
110
+ yaml: "YAML",
111
+ yml: "YAML",
112
+ json: "JSON",
113
+ jsonc: "JSONC",
114
+ sql: "SQL",
115
+ xml: "XML"
116
+ };
117
+ var COPY_ICON = '<svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="8" height="8" rx="1.5"></rect><path d="M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25"></path></svg>';
118
+ function languageLabel(info) {
119
+ const language = info.trim().split(/\s+/, 1)[0]?.toLocaleLowerCase() ?? "";
120
+ if (!language) return "Code";
121
+ return LANGUAGE_LABELS[language] ?? language.toLocaleUpperCase();
122
+ }
123
+ function frameCode(render, tokens, index, options, env, self) {
124
+ const label = markdown.utils.escapeHtml(languageLabel(tokens[index]?.info ?? ""));
125
+ return `<div class="scui-code-block"><div class="scui-code-head"><span>${label}</span><button class="scui-code-copy" type="button" aria-label="Copy code" title="Copy code">${COPY_ICON}<span>Copy</span></button></div>${render(tokens, index, options, env, self)}</div>`;
126
+ }
127
+ for (const kind of ["fence", "code_block"]) {
128
+ const render = markdown.renderer.rules[kind];
129
+ markdown.renderer.rules[kind] = (tokens, index, options, env, self) => frameCode(render, tokens, index, options, env, self);
130
+ }
92
131
  var defaultLinkOpen = markdown.renderer.rules.link_open;
93
132
  markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
94
133
  tokens[index]?.attrSet("target", "_blank");
@@ -96,8 +135,59 @@ markdown.renderer.rules.link_open = (tokens, index, options, env, self) => {
96
135
  return defaultLinkOpen ? defaultLinkOpen(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
97
136
  };
98
137
 
99
- // src/conversation.jsx
138
+ // src/memory.js
139
+ var MEMORY_LIMIT = 100;
140
+ function boundedSet(map, key, value) {
141
+ map.delete(key);
142
+ map.set(key, value);
143
+ while (map.size > MEMORY_LIMIT) map.delete(map.keys().next().value);
144
+ }
145
+
146
+ // src/icon.jsx
100
147
  import { Fragment, jsx as jsx2, jsxs } from "preact/jsx-runtime";
148
+ var ICONS = {
149
+ back: () => /* @__PURE__ */ jsx2("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
150
+ check: () => /* @__PURE__ */ jsx2("path", { d: "m4 9 3.25 3.25L14 5.5" }),
151
+ chevron: () => /* @__PURE__ */ jsx2("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
152
+ close: () => /* @__PURE__ */ jsxs(Fragment, { children: [
153
+ /* @__PURE__ */ jsx2("path", { d: "m4.75 4.75 8.5 8.5" }),
154
+ /* @__PURE__ */ jsx2("path", { d: "m13.25 4.75-8.5 8.5" })
155
+ ] }),
156
+ copy: () => /* @__PURE__ */ jsxs(Fragment, { children: [
157
+ /* @__PURE__ */ jsx2("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1.5" }),
158
+ /* @__PURE__ */ jsx2("path", { d: "M3 10.5V4.25C3 3.56 3.56 3 4.25 3h6.25" })
159
+ ] }),
160
+ down: () => /* @__PURE__ */ jsxs(Fragment, { children: [
161
+ /* @__PURE__ */ jsx2("path", { d: "M9 3.5v10" }),
162
+ /* @__PURE__ */ jsx2("path", { d: "m4.75 9.5 4.25 4 4.25-4" })
163
+ ] }),
164
+ menu: () => /* @__PURE__ */ jsxs(Fragment, { children: [
165
+ /* @__PURE__ */ jsx2("circle", { cx: "4", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
166
+ /* @__PURE__ */ jsx2("circle", { cx: "9", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" }),
167
+ /* @__PURE__ */ jsx2("circle", { cx: "14", cy: "9", r: "1.25", fill: "currentColor", stroke: "none" })
168
+ ] }),
169
+ plus: () => /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ /* @__PURE__ */ jsx2("path", { d: "M9 3.5v11" }),
171
+ /* @__PURE__ */ jsx2("path", { d: "M3.5 9h11" })
172
+ ] }),
173
+ search: () => /* @__PURE__ */ jsxs(Fragment, { children: [
174
+ /* @__PURE__ */ jsx2("circle", { cx: "7.75", cy: "7.75", r: "4.25" }),
175
+ /* @__PURE__ */ jsx2("path", { d: "m11 11 3.5 3.5" })
176
+ ] }),
177
+ send: () => /* @__PURE__ */ jsxs(Fragment, { children: [
178
+ /* @__PURE__ */ jsx2("path", { d: "M9 14.5v-11" }),
179
+ /* @__PURE__ */ jsx2("path", { d: "m4.75 7.75 4.25-4.25 4.25 4.25" })
180
+ ] }),
181
+ stop: () => /* @__PURE__ */ jsx2("rect", { x: "4.5", y: "4.5", width: "9", height: "9", rx: "1.5", fill: "currentColor", stroke: "none" })
182
+ };
183
+ function UiIcon({ name, size = 16, class: className = "" }) {
184
+ const Glyph = ICONS[name];
185
+ if (!Glyph) return null;
186
+ return /* @__PURE__ */ jsx2("svg", { class: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(Glyph, {}) });
187
+ }
188
+
189
+ // src/conversation.jsx
190
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
101
191
  function LoadingStatus({ state, compact = false }) {
102
192
  const copy = {
103
193
  connecting: ["Connecting to coding agents", "Checking installed harnesses and capabilities.", 0],
@@ -105,19 +195,19 @@ function LoadingStatus({ state, compact = false }) {
105
195
  discovering: ["Loading recent sessions", "Scanning native session stores without loading full transcripts.", 2],
106
196
  ready: ["Ready", "Coding sessions are up to date.", 3]
107
197
  }[state.startup];
108
- return /* @__PURE__ */ jsxs("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
109
- /* @__PURE__ */ jsx2("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("i", {}) }),
110
- /* @__PURE__ */ jsxs("span", { class: "scui-loading-copy", children: [
111
- /* @__PURE__ */ jsx2("strong", { children: copy[0] }),
112
- /* @__PURE__ */ jsx2("small", { children: copy[1] })
198
+ return /* @__PURE__ */ jsxs2("div", { class: `scui-loading${compact ? " scui-loading-compact" : ""}`, role: "status", "aria-busy": state.startup !== "ready", children: [
199
+ /* @__PURE__ */ jsx3("span", { class: "scui-orbit", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("i", {}) }),
200
+ /* @__PURE__ */ jsxs2("span", { class: "scui-loading-copy", children: [
201
+ /* @__PURE__ */ jsx3("strong", { children: copy[0] }),
202
+ /* @__PURE__ */ jsx3("small", { children: copy[1] })
113
203
  ] }),
114
- /* @__PURE__ */ jsx2("span", { class: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx2("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
204
+ /* @__PURE__ */ jsx3("span", { class: "scui-progress", "aria-hidden": "true", children: [1, 2, 3].map((step) => /* @__PURE__ */ jsx3("i", { "data-progress": step <= copy[2] ? "done" : step === copy[2] + 1 ? "current" : "waiting" }, step)) })
115
205
  ] });
116
206
  }
117
207
 
118
208
  // src/logo.jsx
119
- import { useEffect as useEffect2 } from "preact/hooks";
120
- import { jsx as jsx3, jsxs as jsxs2 } from "preact/jsx-runtime";
209
+ import { useEffect as useEffect3 } from "preact/hooks";
210
+ import { jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
121
211
  var LOGOS = {
122
212
  "claude-code": {
123
213
  viewBox: "-1 3.5 26 18",
@@ -164,68 +254,90 @@ var LOGOS = {
164
254
  };
165
255
  function HarnessLogo({ id, activity, size = 28, onMissingLogo }) {
166
256
  const logo = LOGOS[id];
167
- useEffect2(() => {
257
+ useEffect3(() => {
168
258
  if (!logo) onMissingLogo?.(id);
169
259
  }, [id, logo, onMissingLogo]);
170
260
  if (!logo) return null;
171
- return /* @__PURE__ */ jsxs2("span", { class: "scui-logo", "data-harness": id, "data-activity": activity, style: `--scui-logo-size:${size}px`, "aria-hidden": "true", children: [
172
- /* @__PURE__ */ jsx3("svg", { viewBox: logo.viewBox, preserveAspectRatio: "xMidYMid meet", focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx3(Tag, { ...props }, index)) }),
173
- activity && activity !== "idle" ? /* @__PURE__ */ jsx3("i", {}) : null
261
+ return /* @__PURE__ */ jsxs3("span", { class: "scui-logo", "data-harness": id, "data-activity": activity, style: `--scui-logo-size:${size}px`, "aria-hidden": "true", children: [
262
+ /* @__PURE__ */ jsx4("svg", { viewBox: logo.viewBox, preserveAspectRatio: "xMidYMid meet", focusable: "false", children: logo.paths.map(([Tag, props], index) => /* @__PURE__ */ jsx4(Tag, { ...props }, index)) }),
263
+ activity && activity !== "idle" ? /* @__PURE__ */ jsx4("i", {}) : null
174
264
  ] });
175
265
  }
176
266
 
177
267
  // src/sessions.jsx
178
- import { jsx as jsx4, jsxs as jsxs3 } from "preact/jsx-runtime";
268
+ import { jsx as jsx5, jsxs as jsxs4 } from "preact/jsx-runtime";
269
+ var sessionListMemory = /* @__PURE__ */ new Map();
179
270
  function SessionRow({ row, state, onOpen }) {
180
271
  const activity = sessionActivity(state, row);
181
272
  const preview = state.attention.find((item) => item.key === row.key)?.preview;
182
- return /* @__PURE__ */ jsxs3("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", onClick: () => onOpen(row), children: [
183
- /* @__PURE__ */ jsx4(HarnessLogo, { id: row.harness, activity, size: 34 }),
184
- /* @__PURE__ */ jsxs3("span", { class: "scui-session-copy", children: [
185
- /* @__PURE__ */ jsxs3("span", { children: [
186
- /* @__PURE__ */ jsx4("strong", { children: sessionDisplayName(row) }),
187
- /* @__PURE__ */ jsx4("small", { children: row.age })
273
+ const title = sessionDisplayName(row);
274
+ const detail = preview || (row.name && row.name !== title ? row.name : row.cwd);
275
+ return /* @__PURE__ */ jsxs4("button", { class: "scui-session", "data-active": row.active, "data-activity": activity, "data-session-key": row.key, type: "button", "aria-label": `${title} \xB7 ${harnessDisplayName(row.harness)}${row.age ? ` \xB7 ${row.age}` : ""}`, "aria-current": row.active ? "true" : void 0, onClick: () => onOpen(row), children: [
276
+ /* @__PURE__ */ jsx5(HarnessLogo, { id: row.harness, activity, size: 34 }),
277
+ /* @__PURE__ */ jsxs4("span", { class: "scui-session-copy", children: [
278
+ /* @__PURE__ */ jsxs4("span", { children: [
279
+ /* @__PURE__ */ jsx5("strong", { children: title }),
280
+ /* @__PURE__ */ jsx5("small", { children: row.age })
188
281
  ] }),
189
- /* @__PURE__ */ jsxs3("span", { children: [
190
- /* @__PURE__ */ jsx4("b", { children: harnessDisplayName(row.harness) }),
191
- /* @__PURE__ */ jsx4("small", { children: preview || row.cwd || row.name })
192
- ] }),
193
- state.attachError?.key === row.key ? /* @__PURE__ */ jsx4("em", { children: state.attachError.message }) : null
282
+ detail ? /* @__PURE__ */ jsx5("span", { children: /* @__PURE__ */ jsx5("small", { title: row.cwd, children: detail }) }) : null,
283
+ state.attachError?.key === row.key ? /* @__PURE__ */ jsx5("em", { children: state.attachError.message }) : null
194
284
  ] })
195
285
  ] });
196
286
  }
197
- function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null }) {
198
- const [query, setQuery] = useState2("");
199
- const root = useRef2(null);
287
+ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {}, labels = DEFAULT_LABELS, focusKey = null, memoryKey = state.workspace || "@default" }) {
288
+ const remembered = sessionListMemory.get(memoryKey) ?? { query: "", top: 0 };
289
+ const [query, setQuery] = useState2(remembered.query);
290
+ const [loadingMore, setLoadingMore] = useState2(false);
291
+ const root = useRef3(null);
292
+ const rowScroller = useRef3(null);
200
293
  const rows = filterSessions(state.sessions, query);
201
294
  const Row = components.SessionRow ?? SessionRow;
202
- useEffect3(() => {
295
+ useLayoutEffect2(() => {
296
+ if (rowScroller.current) rowScroller.current.scrollTop = remembered.top;
297
+ }, [memoryKey]);
298
+ useEffect4(() => {
203
299
  if (!focusKey || !root.current) return;
204
300
  const target = focusKey === "@new" ? root.current.querySelector('[data-list-focus="new"]') : [...root.current.querySelectorAll("[data-session-key]")].find((element) => element.dataset.sessionKey === focusKey);
205
301
  (target ?? root.current.querySelector("input,button"))?.focus({ preventScroll: true });
206
302
  }, [focusKey, rows.length]);
207
- return /* @__PURE__ */ jsxs3("section", { class: "scui-list", ref: root, children: [
208
- /* @__PURE__ */ jsxs3("header", { class: "scui-head", children: [
209
- /* @__PURE__ */ jsxs3("span", { class: "scui-head-copy", children: [
210
- /* @__PURE__ */ jsx4("strong", { children: labels.chats }),
211
- /* @__PURE__ */ jsxs3("small", { children: [
303
+ useEffect4(() => {
304
+ if (loadingMore) setLoadingMore(false);
305
+ }, [state.error, state.history.hasMoreSessions, state.sessions.length]);
306
+ const loadMore = () => {
307
+ if (loadingMore) return;
308
+ setLoadingMore(true);
309
+ const result = adapter.onIntent({ action: "loadSessions" });
310
+ if (result && typeof result.then === "function") {
311
+ Promise.resolve(result).then(() => setLoadingMore(false), () => setLoadingMore(false));
312
+ }
313
+ };
314
+ return /* @__PURE__ */ jsxs4("section", { class: "scui-list", ref: root, children: [
315
+ /* @__PURE__ */ jsxs4("header", { class: "scui-head", children: [
316
+ /* @__PURE__ */ jsxs4("span", { class: "scui-head-copy", children: [
317
+ /* @__PURE__ */ jsx5("strong", { children: labels.chats }),
318
+ /* @__PURE__ */ jsxs4("small", { children: [
212
319
  state.sessions.length,
213
320
  " recent conversations"
214
321
  ] })
215
322
  ] }),
216
- /* @__PURE__ */ jsx4("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: "\uFF0B" }),
217
- onClose ? /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": "Close", onClick: onClose, children: "\xD7" }) : null
323
+ /* @__PURE__ */ jsx5("button", { type: "button", "data-list-focus": "new", "aria-label": labels.newChat, disabled: !state.harnesses.some((item) => item.startable), onClick: onNew, children: /* @__PURE__ */ jsx5(UiIcon, { name: "plus", size: 18 }) }),
324
+ onClose ? /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx5(UiIcon, { name: "close", size: 18 }) }) : null
218
325
  ] }),
219
- state.startup !== "ready" ? /* @__PURE__ */ jsx4(LoadingStatus, { state, compact: rows.length > 0 }) : null,
220
- state.sessions.length > 4 ? /* @__PURE__ */ jsxs3("label", { class: "scui-search", children: [
221
- /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", children: "\u2315" }),
222
- /* @__PURE__ */ jsx4("input", { type: "search", "aria-label": labels.searchChats, placeholder: labels.searchChats, value: query, onInput: (event) => setQuery(event.currentTarget.value) }),
223
- /* @__PURE__ */ jsx4("small", { children: rows.length })
326
+ state.startup !== "ready" ? /* @__PURE__ */ jsx5(LoadingStatus, { state, compact: rows.length > 0 }) : null,
327
+ state.sessions.length > 4 ? /* @__PURE__ */ jsxs4("label", { class: "scui-search", children: [
328
+ /* @__PURE__ */ jsx5(UiIcon, { name: "search", size: 15 }),
329
+ /* @__PURE__ */ jsx5("input", { type: "search", "aria-label": labels.searchChats, placeholder: labels.searchChats, value: query, onInput: (event) => {
330
+ const value = event.currentTarget.value;
331
+ setQuery(value);
332
+ boundedSet(sessionListMemory, memoryKey, { query: value, top: 0 });
333
+ if (rowScroller.current) rowScroller.current.scrollTop = 0;
334
+ } }),
335
+ /* @__PURE__ */ jsx5("small", { children: rows.length })
224
336
  ] }) : null,
225
- /* @__PURE__ */ jsxs3("div", { class: "scui-session-rows", children: [
226
- !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx4("div", { class: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
227
- rows.map((row) => /* @__PURE__ */ jsx4(Row, { value: row, row, state, adapter, onOpen }, row.key)),
228
- state.history.hasMoreSessions ? /* @__PURE__ */ jsx4("button", { class: "scui-load", type: "button", onClick: () => adapter.onIntent({ action: "loadSessions" }), children: "Load older chats" }) : null
337
+ /* @__PURE__ */ jsxs4("div", { class: "scui-session-rows", ref: rowScroller, onScroll: (event) => boundedSet(sessionListMemory, memoryKey, { query, top: event.currentTarget.scrollTop }), children: [
338
+ !rows.length && state.startup === "ready" ? /* @__PURE__ */ jsx5("div", { class: "scui-empty", children: query ? "No chats match your search." : state.error ?? "No coding chats found." }) : null,
339
+ rows.map((row) => /* @__PURE__ */ jsx5(Row, { value: row, row, state, adapter, onOpen }, row.key)),
340
+ state.history.hasMoreSessions ? /* @__PURE__ */ jsx5("button", { class: "scui-load", type: "button", disabled: loadingMore, onClick: loadMore, children: loadingMore ? "Loading older chats\u2026" : "Load older chats" }) : null
229
341
  ] })
230
342
  ] });
231
343
  }
package/styles.css CHANGED
@@ -12,7 +12,7 @@
12
12
  --scui-positive: #22863a;
13
13
  --scui-warning: #b26a00;
14
14
  --scui-danger: #b4232e;
15
- --scui-radius: 14px;
15
+ --scui-radius: 10px;
16
16
  --scui-font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
17
17
  box-sizing: border-box;
18
18
  display: flex;
@@ -46,22 +46,23 @@
46
46
 
47
47
  :where(.scui-root *), :where(.scui-root *::before), :where(.scui-root *::after) { box-sizing: border-box }
48
48
  :where(.scui-root button, .scui-root input, .scui-root textarea, .scui-root select) { font: inherit }
49
+ .scui-icon { display:block; flex:none; width:var(--scui-icon-size); height:var(--scui-icon-size) }
49
50
  :where(.scui-root button) { color: inherit }
50
51
  :where(.scui-list button, .scui-chat button, .scui-request button, .scui-activity button, .scui-compose button, .scui-continuation button) { color: inherit }
51
52
  :where(.scui-root button:focus-visible, .scui-root input:focus-visible, .scui-root textarea:focus-visible, .scui-root select:focus-visible, .scui-root [tabindex]:focus-visible) { outline: 2px solid var(--scui-accent); outline-offset: -2px }
53
+ .scui-sr-only { position:absolute; width:1px; height:1px; padding:0; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0 }
52
54
 
53
55
  .scui-list,.scui-chat { display:flex; flex:1; min-width:0; min-height:0; flex-direction:column }
54
56
  .scui-head { display:flex; flex:none; align-items:center; gap:8px; min-height:49px; padding:7px 8px 7px 12px; border-bottom:1px solid var(--scui-border); background:var(--scui-bg-raised) }
55
57
  .scui-head > .scui-head-copy { display:grid; flex:1; min-width:0 }
56
58
  .scui-head strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px }
57
59
  .scui-head small { overflow:hidden; color:var(--scui-muted); font-size:10.5px; text-overflow:ellipsis; white-space:nowrap }
58
- .scui-head > button,.scui-menu > summary { display:grid; place-items:center; width:30px; height:30px; padding:0; border:0; border-radius:8px; background:transparent; cursor:pointer; font-size:18px; list-style:none }
59
- .scui-head > button:hover,.scui-menu > summary:hover { background:var(--scui-fill) }
60
+ .scui-head > button,.scui-menu-trigger { display:grid; place-items:center; width:30px; height:30px; padding:0; border:0; border-radius:8px; background:transparent; color:inherit; cursor:pointer; font-size:18px }
61
+ .scui-head > button:hover,.scui-menu-trigger:hover,.scui-menu-trigger[aria-expanded="true"] { background:var(--scui-fill) }
60
62
  .scui-menu { position:relative }
61
- .scui-menu > summary::-webkit-details-marker { display:none }
62
- .scui-menu > div { position:absolute; z-index:20; top:36px; right:0; display:grid; width:250px; max-height:300px; padding:5px; overflow:auto; border:1px solid var(--scui-border-strong); border-radius:10px; background:var(--scui-bg); box-shadow:0 10px 28px rgba(0,0,0,.22) }
63
- .scui-menu button { padding:8px; border:0; border-radius:6px; background:transparent; text-align:left; cursor:pointer }
64
- .scui-menu button:hover { background:var(--scui-fill) }
63
+ .scui-menu-panel { position:absolute; z-index:20; top:36px; right:0; display:grid; width:250px; max-height:300px; padding:5px; overflow:auto; border:1px solid var(--scui-border-strong); border-radius:10px; background:var(--scui-bg); box-shadow:0 10px 28px rgba(0,0,0,.22) }
64
+ .scui-menu-panel section { display:grid; gap:1px }.scui-menu-panel section + section { margin-top:4px; padding-top:4px; border-top:1px solid var(--scui-border) }.scui-menu-panel section > strong { padding:4px 8px 2px; color:var(--scui-muted); font-size:9px; font-weight:650; letter-spacing:.04em; text-transform:uppercase }
65
+ .scui-menu-panel button { padding:7px 8px; border:0; border-radius:6px; background:transparent; color:inherit; text-align:left; cursor:pointer }.scui-menu-panel button:hover,.scui-menu-panel button:focus-visible { outline:none; background:var(--scui-fill) }.scui-menu-panel button:disabled { opacity:.45; cursor:default }
65
66
 
66
67
  .scui-logo { position:relative; display:inline-grid; place-items:center; box-sizing:border-box; width:var(--scui-logo-size); height:var(--scui-logo-size); flex:none; border:1px solid var(--scui-border); border-radius:50%; background:#111; color:#f7f7f5 }
67
68
  .scui-logo svg { display:block; width:70%; height:70%; overflow:visible; fill:currentColor }
@@ -91,16 +92,15 @@
91
92
  .scui-search { display:flex; flex:none; align-items:center; gap:7px; margin:8px; padding:6px 8px; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-bg-raised); color:var(--scui-muted) }
92
93
  .scui-search input { flex:1; min-width:0; border:0; outline:0; background:transparent; color:var(--scui-fg) }
93
94
  .scui-session-rows { flex:1; min-height:0; overflow:auto; padding:0 6px 8px }
94
- .scui-session { display:flex; align-items:center; gap:9px; width:100%; padding:7px 8px; border:0; border-radius:9px; background:transparent; text-align:left; cursor:pointer }
95
+ .scui-session { display:flex; align-items:center; gap:9px; width:100%; padding:7px 8px; border:0; border-radius:7px; background:transparent; text-align:left; cursor:pointer }
95
96
  .scui-session:hover,.scui-session[data-active="true"] { background:var(--scui-fill) }
96
97
  .scui-session-copy { display:grid; flex:1; min-width:0; gap:2px }
97
98
  .scui-session-copy > span { display:flex; align-items:baseline; gap:7px; min-width:0 }
98
99
  .scui-session-copy strong,.scui-session-copy span > small { overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
99
100
  .scui-session-copy strong { flex:1; font-size:12.5px }.scui-session-copy span:first-child > small { flex:none; color:var(--scui-muted); font-size:10px }
100
- .scui-session-copy b { flex:none; padding:1px 5px; border-radius:4px; background:var(--scui-bg-raised); color:var(--scui-muted); font-size:9px; text-transform:uppercase }
101
- .scui-session-copy span:last-child > small { color:var(--scui-muted); font-size:10.5px }
101
+ .scui-session-copy span:last-child > small { min-width:0; overflow:hidden; color:var(--scui-muted); font-size:10.5px; text-overflow:ellipsis; white-space:nowrap }
102
102
  .scui-session-copy em { overflow:hidden; color:var(--scui-danger); font-size:10px; font-style:normal; text-overflow:ellipsis; white-space:nowrap }
103
- .scui-load { display:block; margin:7px auto; padding:5px 10px; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-bg-raised); color:var(--scui-muted); cursor:pointer }
103
+ .scui-load { display:block; margin:7px auto; padding:5px 10px; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-bg-raised); color:var(--scui-muted); cursor:pointer }.scui-load:disabled { opacity:.65; cursor:default }
104
104
 
105
105
  .scui-operation,.scui-error,.scui-receipt { display:flex; flex:none; align-items:center; gap:8px; min-height:32px; padding:6px 10px; border-bottom:1px solid var(--scui-border); background:var(--scui-bg-raised); font-size:10.5px }
106
106
  .scui-operation i { width:11px; height:11px; border:2px solid var(--scui-border); border-top-color:var(--scui-accent); border-radius:50%; animation:scui-spin .8s linear infinite }
@@ -112,18 +112,26 @@
112
112
  .scui-conversation { flex:1; min-width:0; overflow:auto; overscroll-behavior:contain; padding:12px 16px 18px }
113
113
  .scui-conversation > div { display:flex; flex-direction:column; gap:10px }
114
114
  .scui-empty { margin:auto; padding:20px; color:var(--scui-muted); text-align:center }
115
- .scui-latest { position:absolute; z-index:5; right:12px; bottom:8px; padding:5px 9px; border:1px solid var(--scui-border-strong); border-radius:999px; background:var(--scui-bg-raised); box-shadow:0 2px 8px rgba(0,0,0,.2); cursor:pointer }
116
- .scui-message { position:relative; overflow-wrap:anywhere }
115
+ .scui-latest { position:absolute; z-index:5; right:12px; bottom:8px; display:flex; align-items:center; gap:3px; padding:5px 9px; border:1px solid var(--scui-border-strong); border-radius:999px; background:var(--scui-bg-raised); box-shadow:0 2px 8px rgba(0,0,0,.2); cursor:pointer }
116
+ .scui-message { position:relative; min-width:0; max-width:100%; overflow-wrap:anywhere }
117
117
  .scui-message[data-role="user"] { align-self:flex-end; max-width:90%; padding:8px 9px; border:1px solid var(--scui-border); border-radius:10px; background:var(--scui-fill) }
118
+ .scui-message-meta { position:absolute; z-index:2; top:-10px; right:0; display:flex; align-items:center; gap:3px; min-height:20px; padding:1px 3px 1px 6px; border:1px solid var(--scui-border); border-radius:7px; background:var(--scui-bg-raised); box-shadow:0 2px 7px rgba(0,0,0,.14); color:var(--scui-muted); opacity:0; pointer-events:none; transition:opacity 100ms }
119
+ .scui-message:hover > .scui-message-meta,.scui-message:focus-within > .scui-message-meta { opacity:1; pointer-events:auto }.scui-message-meta time { font:9px ui-monospace,SFMono-Regular,Menlo,monospace; white-space:nowrap }.scui-message-meta button { display:grid; width:20px; height:20px; padding:3px; place-items:center; border:0; border-radius:5px; background:transparent; color:inherit; cursor:pointer }.scui-message-meta button:hover,.scui-message-meta button:focus-visible { background:var(--scui-fill); color:var(--scui-fg) }.scui-message-meta button[data-status="copied"] { color:var(--scui-success) }.scui-message-meta button[data-status="failed"] { color:var(--scui-danger) }.scui-message-meta svg { width:13px; height:13px; fill:none; stroke:currentColor; stroke-width:1.25; stroke-linecap:round; stroke-linejoin:round }
120
+ @media (hover:none) { .scui-message-meta { opacity:.82; pointer-events:auto } }
118
121
  .scui-pending[data-status="failed"] { border-color:color-mix(in srgb,var(--scui-danger) 45%,var(--scui-border)); background:color-mix(in srgb,var(--scui-danger) 7%,var(--scui-fill)); opacity:1 }
119
122
  .scui-pending > footer { display:flex; align-items:center; gap:8px; margin-top:5px }.scui-pending > footer small { color:var(--scui-muted) }.scui-pending[data-status="failed"] > footer small { color:var(--scui-danger) }
120
123
  .scui-pending > footer > span { display:flex; gap:4px; margin-left:auto }.scui-pending > footer button { padding:2px 6px; border:1px solid var(--scui-border-strong); border-radius:5px; background:transparent; color:var(--scui-fg); cursor:pointer; font:inherit; font-size:9.5px }
121
124
  .scui-pending { opacity:.7 }.scui-pending > small { display:block; margin-top:3px; color:var(--scui-muted); font-size:9.5px }
122
- .scui-markdown { font-size:12.5px; line-height:1.55 }.scui-markdown > :first-child { margin-top:0 }.scui-markdown > :last-child { margin-bottom:0 }
125
+ .scui-markdown { min-width:0; font-size:12.5px; line-height:1.55 }.scui-markdown > :first-child { margin-top:0 }.scui-markdown > :last-child { margin-bottom:0 }
123
126
  .scui-markdown p { margin:0 0 8px }.scui-markdown h1,.scui-markdown h2,.scui-markdown h3 { margin:12px 0 5px; line-height:1.25 }.scui-markdown h1 { font-size:17px }.scui-markdown h2 { font-size:15px }.scui-markdown h3 { font-size:13px }
124
127
  .scui-markdown ul,.scui-markdown ol { margin:5px 0 9px; padding-left:20px }.scui-markdown blockquote { margin:8px 0; padding-left:9px; border-left:2px solid var(--scui-border-strong); color:var(--scui-muted) }
125
128
  .scui-markdown a { color:var(--scui-accent) }.scui-markdown code { padding:1px 4px; border-radius:4px; background:var(--scui-fill); font:.92em ui-monospace,SFMono-Regular,Menlo,monospace }
126
- .scui-markdown pre { padding:8px; overflow:auto; border:1px solid var(--scui-border); border-radius:7px; background:var(--scui-bg-raised) }.scui-markdown pre code { padding:0; background:transparent }
129
+ .scui-code-block { min-width:0; margin:8px 0; overflow:hidden; border:1px solid var(--scui-border); border-radius:8px; background:var(--scui-bg-raised) }
130
+ .scui-code-head { display:flex; align-items:center; min-height:28px; padding:0 4px 0 9px; border-bottom:1px solid var(--scui-border); color:var(--scui-muted); font-size:9.5px; font-weight:600; letter-spacing:.025em }
131
+ .scui-code-head > span { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
132
+ .scui-code-copy { display:flex; align-items:center; gap:4px; min-width:28px; min-height:24px; padding:0 5px; border:0; border-radius:5px; background:transparent; color:var(--scui-muted); cursor:pointer; font-size:9.5px }.scui-code-copy:hover,.scui-code-copy:focus-visible { background:var(--scui-fill); color:var(--scui-fg) }.scui-code-copy:disabled { cursor:wait }.scui-code-copy[data-status="copied"] { color:var(--scui-positive) }.scui-code-copy[data-status="failed"] { color:var(--scui-danger) }.scui-code-copy svg { width:13px; height:13px; flex:none }
133
+ .scui-markdown[data-copyable="false"] .scui-code-copy { display:none }
134
+ .scui-markdown pre { max-width:100%; margin:0; padding:9px 10px; overflow:auto; overscroll-behavior:contain; scrollbar-gutter:stable; background:transparent }.scui-markdown pre code { padding:0; background:transparent; overflow-wrap:normal; word-break:normal; white-space:pre }
127
135
  .scui-markdown table { display:block; overflow:auto; border-collapse:collapse }.scui-markdown th,.scui-markdown td { padding:4px; border:1px solid var(--scui-border) }
128
136
  .scui-context,.scui-reasoning,.scui-details,.scui-plan { color:var(--scui-muted); font-size:10.5px }
129
137
  .scui-context summary,.scui-reasoning summary,.scui-details summary,.scui-plan summary { cursor:pointer }
@@ -131,11 +139,12 @@
131
139
  .scui-context p,.scui-details p { display:grid; margin:0 }.scui-context strong,.scui-details strong { color:var(--scui-fg) }
132
140
  .scui-truncated { display:block; margin-top:5px; color:var(--scui-warning) }
133
141
  .scui-notice { align-self:center; max-width:90%; padding:5px 8px; border-radius:7px; background:var(--scui-bg-raised); color:var(--scui-muted); font-size:10.5px; text-align:center }
142
+ .scui-unread-divider { display:flex; align-items:center; gap:7px; color:var(--scui-accent); font-size:9.5px; font-weight:650 }.scui-unread-divider::before,.scui-unread-divider::after { content:""; flex:1; height:1px; background:color-mix(in srgb,var(--scui-accent) 45%,var(--scui-border)) }.scui-unread-divider span { flex:none }
134
143
 
135
144
  .scui-activity { min-width:0; color:var(--scui-muted) }
136
145
  .scui-activity-head,.scui-tool-head { display:flex; align-items:center; gap:6px; box-sizing:border-box; width:100%; min-height:27px; padding:0 3px; border:0; background:transparent; color:inherit; text-align:left }
137
146
  .scui-activity-head { cursor:pointer }.scui-activity-head strong { color:var(--scui-muted); font-size:10.5px; font-weight:600 }.scui-activity-head small { color:var(--scui-muted) }.scui-spacer { flex:1 }
138
- .scui-fold { font-size:16px; transition:transform 120ms }.scui-fold[data-open="true"] { transform:rotate(90deg) }
147
+ .scui-fold { display:grid; flex:none; place-items:center; transition:transform 120ms }.scui-fold[data-open="true"] { transform:rotate(90deg) }
139
148
  .scui-activity:not([data-single="true"]) > div { margin-left:9px; padding-left:7px; border-left:1px solid var(--scui-border) }
140
149
  .scui-tool { min-width:0 }.scui-tool summary { list-style:none }.scui-tool summary::-webkit-details-marker { display:none }.scui-tool-head { cursor:default }.scui-tool > summary.scui-tool-head { cursor:pointer }.scui-tool-icon { flex:0 0 14px; width:14px; height:14px; color:var(--scui-muted) }.scui-tool-head strong { flex:none; color:var(--scui-fg); font-size:10.5px; font-weight:600; white-space:nowrap }
141
150
  .scui-tool-target { min-width:0; overflow:hidden; padding:0; background:transparent; color:var(--scui-muted); font:10px ui-monospace,SFMono-Regular,Menlo,monospace; text-overflow:ellipsis; white-space:nowrap }
@@ -160,8 +169,8 @@
160
169
 
161
170
  .scui-continuation { display:flex; align-items:center; gap:8px; padding:7px 9px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }.scui-continuation > span { display:grid; flex:1 }.scui-continuation small { color:var(--scui-muted); font-size:10px }.scui-continuation button { padding:5px 8px; border:1px solid var(--scui-accent); border-radius:7px; background:transparent; color:var(--scui-accent); cursor:pointer }
162
171
  .scui-compose { flex:none; padding:8px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }
163
- .scui-envelope { display:flex; align-items:flex-end; gap:7px; padding:7px; border:1px solid var(--scui-border-strong); border-radius:10px; background:var(--scui-bg) }.scui-envelope textarea { flex:1; min-width:0; min-height:34px; max-height:150px; resize:none; border:0; outline:0; background:transparent; color:var(--scui-fg) }.scui-envelope > span { display:flex; gap:4px }
164
- .scui-send,.scui-stop { width:29px; height:29px; border:0; border-radius:8px; background:var(--scui-accent); color:#fff; cursor:pointer }.scui-stop { background:var(--scui-danger) }.scui-send:disabled,.scui-stop:disabled { opacity:.4; cursor:default }
172
+ .scui-envelope { display:flex; align-items:flex-end; gap:7px; padding:7px; border:1px solid var(--scui-border-strong); border-radius:16px; background:var(--scui-bg) }.scui-envelope textarea { flex:1; min-width:0; min-height:34px; max-height:150px; resize:none; overflow-y:hidden; border:0; outline:0; background:transparent; color:var(--scui-fg) }.scui-envelope > span { display:flex; gap:4px }
173
+ .scui-send,.scui-stop { display:grid; width:29px; height:29px; padding:0; place-items:center; border:0; border-radius:8px; background:var(--scui-accent); color:#fff; cursor:pointer }.scui-stop { background:var(--scui-danger) }.scui-send:disabled,.scui-stop:disabled { opacity:.4; cursor:default }.scui-control-spinner { box-sizing:border-box; width:13px; height:13px; border:1.5px solid currentColor; border-right-color:transparent; border-radius:50%; animation:scui-spin .75s linear infinite }
165
174
  .scui-queue { display:grid; gap:4px; max-height:85px; margin-bottom:6px; overflow:auto; font-size:10.5px }.scui-queue > span { display:flex; gap:6px; padding:4px 6px; border-radius:5px; background:var(--scui-fill) }.scui-queue > span button { margin-left:auto; border:0; background:transparent }
166
175
  .scui-harness-picker { display:flex; align-items:center; gap:7px; margin-bottom:7px }.scui-harness-picker select { margin-left:auto; max-width:55%; padding:4px; border:1px solid var(--scui-border); border-radius:6px; background:var(--scui-bg); color:var(--scui-fg) }
167
176
  .scui-new { display:grid; justify-items:center; gap:5px; margin:auto; padding:20px; color:var(--scui-muted); text-align:center }.scui-new > span { color:var(--scui-accent); font-size:28px }.scui-new strong { color:var(--scui-fg) }
@@ -172,3 +181,11 @@
172
181
  @keyframes scui-progress { from { transform:translateX(-100%) } to { transform:translateX(180%) } }
173
182
  @keyframes scui-dots { 50% { transform:translateY(-3px); opacity:.45 } }
174
183
  @media (prefers-reduced-motion:reduce) { .scui-root * { animation-duration:.001ms !important; animation-iteration-count:1 !important; transition:none !important } }
184
+ @media (pointer:coarse) {
185
+ .scui-head > button,.scui-menu-trigger { width:40px; height:40px }
186
+ .scui-head { min-height:52px }
187
+ .scui-menu-panel button,.scui-request-actions button,.scui-load { min-height:40px }
188
+ .scui-send,.scui-stop { width:40px; height:40px }
189
+ .scui-message-meta button,.scui-code-copy { min-width:36px; min-height:36px }
190
+ .scui-latest { min-height:36px; padding-inline:12px }
191
+ }