@cookielab.io/klovi 0.12.0 → 0.13.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.
@@ -40924,11 +40924,44 @@ var init_all = __esm(() => {
40924
40924
  var favicon_default = "./favicon-d3ne5n0v.svg";
40925
40925
 
40926
40926
  // src/frontend/App.tsx
40927
- var import_react22 = __toESM(require_react(), 1);
40927
+ var import_react16 = __toESM(require_react(), 1);
40928
40928
  var import_client = __toESM(require_client(), 1);
40929
40929
 
40930
- // src/frontend/components/dashboard/DashboardStats.tsx
40930
+ // src/frontend/hooks/useFetch.ts
40931
40931
  var import_react = __toESM(require_react(), 1);
40932
+ function useFetch(url, deps) {
40933
+ const [data, setData] = import_react.useState(null);
40934
+ const [loading, setLoading] = import_react.useState(true);
40935
+ const [error, setError] = import_react.useState(null);
40936
+ const [retryCount, setRetryCount] = import_react.useState(0);
40937
+ const retry = import_react.useCallback(() => setRetryCount((c) => c + 1), []);
40938
+ import_react.useEffect(() => {
40939
+ let cancelled = false;
40940
+ setLoading(true);
40941
+ setError(null);
40942
+ fetch(url).then((r) => {
40943
+ if (!r.ok)
40944
+ throw new Error(`HTTP ${r.status}`);
40945
+ return r.json();
40946
+ }).then((result) => {
40947
+ if (!cancelled) {
40948
+ setData(result);
40949
+ setLoading(false);
40950
+ }
40951
+ }).catch((e) => {
40952
+ if (!cancelled) {
40953
+ setError(e instanceof Error ? e.message : String(e));
40954
+ setLoading(false);
40955
+ }
40956
+ });
40957
+ return () => {
40958
+ cancelled = true;
40959
+ };
40960
+ }, [url, retryCount, ...deps]);
40961
+ return { data, loading, error, retry };
40962
+ }
40963
+
40964
+ // src/frontend/components/dashboard/DashboardStats.tsx
40932
40965
  var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
40933
40966
  var fmt = new Intl.NumberFormat;
40934
40967
  function compactNumber(n) {
@@ -40944,12 +40977,11 @@ function simplifyModelName(model) {
40944
40977
  const match = model.match(/claude-(\w+-[\d-]+?)(?:-\d{8})?$/);
40945
40978
  return match?.[1] ?? model;
40946
40979
  }
40980
+ function totalTokens(usage) {
40981
+ return usage.inputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheCreationTokens;
40982
+ }
40947
40983
  function DashboardStats() {
40948
- const [stats, setStats] = import_react.useState(null);
40949
- const [loading, setLoading] = import_react.useState(true);
40950
- import_react.useEffect(() => {
40951
- fetch("/api/stats").then((r) => r.json()).then((data) => setStats(data.stats)).catch(() => {}).finally(() => setLoading(false));
40952
- }, []);
40984
+ const { data, loading, error, retry } = useFetch("/api/stats", []);
40953
40985
  if (loading) {
40954
40986
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
40955
40987
  className: "dashboard-stats",
@@ -40961,9 +40993,27 @@ function DashboardStats() {
40961
40993
  }, undefined, false, undefined, this)
40962
40994
  }, undefined, false, undefined, this);
40963
40995
  }
40996
+ if (error) {
40997
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
40998
+ className: "fetch-error",
40999
+ children: [
41000
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", {
41001
+ className: "fetch-error-message",
41002
+ children: error
41003
+ }, undefined, false, undefined, this),
41004
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("button", {
41005
+ type: "button",
41006
+ className: "btn btn-sm",
41007
+ onClick: retry,
41008
+ children: "Retry"
41009
+ }, undefined, false, undefined, this)
41010
+ ]
41011
+ }, undefined, true, undefined, this);
41012
+ }
41013
+ const stats = data?.stats;
40964
41014
  if (!stats)
40965
41015
  return null;
40966
- const sortedModels = Object.entries(stats.models).sort((a, b) => b[1] - a[1]);
41016
+ const sortedModels = Object.entries(stats.models).sort((a, b) => totalTokens(b[1]) - totalTokens(a[1]));
40967
41017
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
40968
41018
  className: "dashboard-stats",
40969
41019
  children: [
@@ -40996,6 +41046,50 @@ function DashboardStats() {
40996
41046
  }, undefined, false, undefined, this)
40997
41047
  ]
40998
41048
  }, undefined, true, undefined, this),
41049
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41050
+ className: "stat-card",
41051
+ children: [
41052
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41053
+ className: "stat-value",
41054
+ children: fmt.format(stats.messages)
41055
+ }, undefined, false, undefined, this),
41056
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41057
+ className: "stat-label",
41058
+ children: "Messages"
41059
+ }, undefined, false, undefined, this)
41060
+ ]
41061
+ }, undefined, true, undefined, this)
41062
+ ]
41063
+ }, undefined, true, undefined, this),
41064
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41065
+ className: "stats-row stats-row-3",
41066
+ children: [
41067
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41068
+ className: "stat-card",
41069
+ children: [
41070
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41071
+ className: "stat-value",
41072
+ children: fmt.format(stats.todaySessions)
41073
+ }, undefined, false, undefined, this),
41074
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41075
+ className: "stat-label",
41076
+ children: "Today Sessions"
41077
+ }, undefined, false, undefined, this)
41078
+ ]
41079
+ }, undefined, true, undefined, this),
41080
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41081
+ className: "stat-card",
41082
+ children: [
41083
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41084
+ className: "stat-value",
41085
+ children: fmt.format(stats.thisWeekSessions)
41086
+ }, undefined, false, undefined, this),
41087
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41088
+ className: "stat-label",
41089
+ children: "This Week"
41090
+ }, undefined, false, undefined, this)
41091
+ ]
41092
+ }, undefined, true, undefined, this),
40999
41093
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
41000
41094
  className: "stat-card",
41001
41095
  children: [
@@ -41086,7 +41180,7 @@ function DashboardStats() {
41086
41180
  }, undefined, false, undefined, this),
41087
41181
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV("ul", {
41088
41182
  className: "model-list",
41089
- children: sortedModels.map(([model, count]) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV("li", {
41183
+ children: sortedModels.map(([model, usage]) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV("li", {
41090
41184
  children: [
41091
41185
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", {
41092
41186
  className: "model-name",
@@ -41095,8 +41189,8 @@ function DashboardStats() {
41095
41189
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV("span", {
41096
41190
  className: "model-count",
41097
41191
  children: [
41098
- fmt.format(count),
41099
- " turns"
41192
+ compactNumber(totalTokens(usage)),
41193
+ " tokens"
41100
41194
  ]
41101
41195
  }, undefined, true, undefined, this)
41102
41196
  ]
@@ -41255,13 +41349,9 @@ function Header({
41255
41349
  }
41256
41350
 
41257
41351
  // src/frontend/components/layout/Sidebar.tsx
41258
- var import_react3 = __toESM(require_react(), 1);
41259
41352
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
41260
41353
  function Sidebar({ children, onSearchClick }) {
41261
- const [versionInfo, setVersionInfo] = import_react3.useState(null);
41262
- import_react3.useEffect(() => {
41263
- fetch("/api/version").then((res) => res.json()).then((data) => setVersionInfo(data)).catch(() => {});
41264
- }, []);
41354
+ const { data: versionInfo } = useFetch("/api/version", []);
41265
41355
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV("div", {
41266
41356
  className: "sidebar",
41267
41357
  children: [
@@ -41332,9 +41422,6 @@ function Layout({ sidebar, hideSidebar, onSearchClick, children }) {
41332
41422
  }, undefined, true, undefined, this);
41333
41423
  }
41334
41424
 
41335
- // src/frontend/components/message/SubAgentView.tsx
41336
- var import_react9 = __toESM(require_react(), 1);
41337
-
41338
41425
  // src/frontend/utils/time.ts
41339
41426
  function formatTimestamp(iso) {
41340
41427
  const date = new Date(iso);
@@ -41397,8 +41484,79 @@ function formatTime(iso) {
41397
41484
  });
41398
41485
  }
41399
41486
 
41487
+ // src/frontend/components/ui/ErrorBoundary.tsx
41488
+ var import_react3 = __toESM(require_react(), 1);
41489
+ var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
41490
+
41491
+ class ErrorBoundary extends import_react3.Component {
41492
+ state = { error: null };
41493
+ static getDerivedStateFromError(error) {
41494
+ return { error };
41495
+ }
41496
+ retry = () => {
41497
+ this.setState({ error: null });
41498
+ };
41499
+ render() {
41500
+ const { error } = this.state;
41501
+ if (!error)
41502
+ return this.props.children;
41503
+ if (this.props.inline) {
41504
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
41505
+ className: "error-card",
41506
+ children: [
41507
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
41508
+ className: "error-card-header",
41509
+ children: [
41510
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("span", {
41511
+ className: "error-card-title",
41512
+ children: "Failed to render"
41513
+ }, undefined, false, undefined, this),
41514
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("button", {
41515
+ type: "button",
41516
+ className: "btn btn-sm",
41517
+ onClick: this.retry,
41518
+ children: "Retry"
41519
+ }, undefined, false, undefined, this)
41520
+ ]
41521
+ }, undefined, true, undefined, this),
41522
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("details", {
41523
+ className: "error-card-details",
41524
+ children: [
41525
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("summary", {
41526
+ children: "Error details"
41527
+ }, undefined, false, undefined, this),
41528
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("pre", {
41529
+ children: error.stack || error.message
41530
+ }, undefined, false, undefined, this)
41531
+ ]
41532
+ }, undefined, true, undefined, this)
41533
+ ]
41534
+ }, undefined, true, undefined, this);
41535
+ }
41536
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
41537
+ className: "error-view",
41538
+ children: [
41539
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
41540
+ className: "error-view-title",
41541
+ children: "Something went wrong"
41542
+ }, undefined, false, undefined, this),
41543
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
41544
+ className: "error-view-message",
41545
+ children: error.message
41546
+ }, undefined, false, undefined, this),
41547
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("button", {
41548
+ type: "button",
41549
+ className: "btn btn-primary",
41550
+ onClick: this.retry,
41551
+ children: "Try Again"
41552
+ }, undefined, false, undefined, this)
41553
+ ]
41554
+ }, undefined, true, undefined, this);
41555
+ }
41556
+ }
41557
+
41400
41558
  // src/frontend/components/ui/MarkdownRenderer.tsx
41401
- var import_react7 = __toESM(require_react(), 1);
41559
+ var import_react8 = __toESM(require_react(), 1);
41402
41560
 
41403
41561
  // node_modules/devlop/lib/development.js
41404
41562
  var codesWarned = new Set;
@@ -41728,12 +41886,12 @@ function productionCreate(_, jsx, jsxs) {
41728
41886
  return key ? fn(type, props, key) : fn(type, props);
41729
41887
  }
41730
41888
  }
41731
- function developmentCreate(filePath, jsxDEV5) {
41889
+ function developmentCreate(filePath, jsxDEV6) {
41732
41890
  return create2;
41733
41891
  function create2(node, type, props, key) {
41734
41892
  const isStaticChildren = Array.isArray(props.children);
41735
41893
  const point3 = pointStart(node);
41736
- return jsxDEV5(type, props, key, isStaticChildren, {
41894
+ return jsxDEV6(type, props, key, isStaticChildren, {
41737
41895
  columnNumber: point3 ? point3.column - 1 : undefined,
41738
41896
  fileName: filePath,
41739
41897
  lineNumber: point3 ? point3.line : undefined
@@ -53101,210 +53259,1010 @@ var one_dark_default = {
53101
53259
  stroke: "hsl(220, 14%, 71%)"
53102
53260
  }
53103
53261
  };
53104
- // src/frontend/components/ui/CodeBlock.tsx
53105
- var jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
53106
- function CodeBlock({ language, children }) {
53107
- const lang = language || "text";
53108
- return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
53109
- className: "code-block-wrapper",
53110
- children: [
53111
- language && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
53112
- className: "code-block-header",
53113
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("span", {
53114
- children: language
53115
- }, undefined, false, undefined, this)
53116
- }, undefined, false, undefined, this),
53117
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV("div", {
53118
- className: "code-block-content",
53119
- children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(prism_default2, {
53120
- language: lang,
53121
- style: one_dark_default,
53122
- customStyle: {
53123
- margin: 0,
53124
- fontSize: "0.85em",
53125
- lineHeight: 1.5
53126
- },
53127
- showLineNumbers: children.split(`
53128
- `).length > 3,
53129
- children: children.replace(/\n$/, "")
53130
- }, undefined, false, undefined, this)
53131
- }, undefined, false, undefined, this)
53132
- ]
53133
- }, undefined, true, undefined, this);
53134
- }
53135
-
53136
- // src/frontend/components/ui/MarkdownRenderer.tsx
53137
- var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
53138
- var FILE_REF_RE = /@([\w./-]+\.\w+)/g;
53139
- function renderTextWithFileRefs(text7) {
53140
- const parts = [];
53141
- let last = 0;
53142
- FILE_REF_RE.lastIndex = 0;
53143
- let match = FILE_REF_RE.exec(text7);
53144
- while (match !== null) {
53145
- if (match.index > last) {
53146
- parts.push(text7.slice(last, match.index));
53147
- }
53148
- parts.push(/* @__PURE__ */ jsx_dev_runtime6.jsxDEV("code", {
53149
- className: "file-ref",
53150
- children: [
53151
- "@",
53152
- match[1]
53153
- ]
53154
- }, match.index, true, undefined, this));
53155
- last = FILE_REF_RE.lastIndex;
53156
- match = FILE_REF_RE.exec(text7);
53157
- }
53158
- if (parts.length === 0)
53159
- return text7;
53160
- if (last < text7.length)
53161
- parts.push(text7.slice(last));
53162
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(jsx_dev_runtime6.Fragment, {
53163
- children: parts
53164
- }, undefined, false, undefined, this);
53165
- }
53166
- function MarkdownRenderer({ content: content3 }) {
53167
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("div", {
53168
- className: "markdown-content",
53169
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Markdown, {
53170
- remarkPlugins: [remarkGfm],
53171
- components: {
53172
- p({ children }) {
53173
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("p", {
53174
- children: import_react7.default.Children.map(children, (child) => typeof child === "string" ? renderTextWithFileRefs(child) : child)
53175
- }, undefined, false, undefined, this);
53176
- },
53177
- code({ className, children, ...props }) {
53178
- const match = /language-(\w+)/.exec(className || "");
53179
- const text7 = String(children).replace(/\n$/, "");
53180
- if (!match && !text7.includes(`
53181
- `)) {
53182
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("code", {
53183
- className,
53184
- ...props,
53185
- children
53186
- }, undefined, false, undefined, this);
53187
- }
53188
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(CodeBlock, {
53189
- language: match?.[1],
53190
- children: text7
53191
- }, undefined, false, undefined, this);
53192
- },
53193
- a({ href, children, ...props }) {
53194
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("a", {
53195
- href,
53196
- target: "_blank",
53197
- rel: "noopener noreferrer",
53198
- ...props,
53199
- children
53200
- }, undefined, false, undefined, this);
53201
- }
53202
- },
53203
- children: content3
53204
- }, undefined, false, undefined, this)
53205
- }, undefined, false, undefined, this);
53206
- }
53207
-
53208
- // src/shared/content-blocks.ts
53209
- function groupContentBlocks(blocks) {
53210
- const groups = [];
53211
- let nonTextGroup = [];
53212
- for (const block of blocks) {
53213
- if (block.type === "text") {
53214
- if (nonTextGroup.length > 0) {
53215
- groups.push(nonTextGroup);
53216
- nonTextGroup = [];
53217
- }
53218
- groups.push([block]);
53219
- } else {
53220
- nonTextGroup.push(block);
53221
- }
53222
- }
53223
- if (nonTextGroup.length > 0) {
53224
- groups.push(nonTextGroup);
53225
- }
53226
- return groups;
53227
- }
53228
-
53229
- // src/frontend/utils/model.ts
53230
- function shortModel(model) {
53231
- const match = model.match(/claude-(opus|sonnet|haiku)-(\d+)(?:-(\d{1,2}))?(?:-\d{8,})?$/);
53232
- if (match) {
53233
- const family = match[1].charAt(0).toUpperCase() + match[1].slice(1);
53234
- const major = match[2];
53235
- const minor = match[3];
53236
- return minor ? `${family} ${major}.${minor}` : `${family} ${major}`;
53237
- }
53238
- return model;
53239
- }
53240
-
53241
- // src/frontend/components/ui/CollapsibleSection.tsx
53242
- var import_react8 = __toESM(require_react(), 1);
53243
- var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
53244
- function CollapsibleSection({
53245
- title,
53246
- defaultOpen = false,
53247
- children
53248
- }) {
53249
- const [open, setOpen] = import_react8.useState(defaultOpen);
53250
- return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("div", {
53251
- className: "collapsible",
53252
- children: [
53253
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("button", {
53254
- type: "button",
53255
- className: "collapsible-header",
53256
- onClick: () => setOpen(!open),
53257
- children: [
53258
- /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("span", {
53259
- className: `collapsible-chevron ${open ? "open" : ""}`,
53260
- children: "▶"
53261
- }, undefined, false, undefined, this),
53262
- title
53263
- ]
53264
- }, undefined, true, undefined, this),
53265
- open && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("div", {
53266
- className: "collapsible-content",
53267
- children
53268
- }, undefined, false, undefined, this)
53269
- ]
53270
- }, undefined, true, undefined, this);
53271
- }
53272
-
53273
- // src/frontend/components/message/ToolCall.tsx
53274
- var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
53275
- var MAX_OUTPUT_LENGTH = 5000;
53276
- var MAX_CONTENT_LENGTH = 2000;
53277
- var MAX_THINKING_PREVIEW = 100;
53278
- function ToolCall({ call, sessionId, project }) {
53262
+ // node_modules/react-syntax-highlighter/dist/esm/styles/prism/one-light.js
53263
+ var one_light_default = {
53264
+ 'code[class*="language-"]': {
53265
+ background: "hsl(230, 1%, 98%)",
53266
+ color: "hsl(230, 8%, 24%)",
53267
+ fontFamily: '"Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace',
53268
+ direction: "ltr",
53269
+ textAlign: "left",
53270
+ whiteSpace: "pre",
53271
+ wordSpacing: "normal",
53272
+ wordBreak: "normal",
53273
+ lineHeight: "1.5",
53274
+ MozTabSize: "2",
53275
+ OTabSize: "2",
53276
+ tabSize: "2",
53277
+ WebkitHyphens: "none",
53278
+ MozHyphens: "none",
53279
+ msHyphens: "none",
53280
+ hyphens: "none"
53281
+ },
53282
+ 'pre[class*="language-"]': {
53283
+ background: "hsl(230, 1%, 98%)",
53284
+ color: "hsl(230, 8%, 24%)",
53285
+ fontFamily: '"Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace',
53286
+ direction: "ltr",
53287
+ textAlign: "left",
53288
+ whiteSpace: "pre",
53289
+ wordSpacing: "normal",
53290
+ wordBreak: "normal",
53291
+ lineHeight: "1.5",
53292
+ MozTabSize: "2",
53293
+ OTabSize: "2",
53294
+ tabSize: "2",
53295
+ WebkitHyphens: "none",
53296
+ MozHyphens: "none",
53297
+ msHyphens: "none",
53298
+ hyphens: "none",
53299
+ padding: "1em",
53300
+ margin: "0.5em 0",
53301
+ overflow: "auto",
53302
+ borderRadius: "0.3em"
53303
+ },
53304
+ 'code[class*="language-"]::-moz-selection': {
53305
+ background: "hsl(230, 1%, 90%)",
53306
+ color: "inherit"
53307
+ },
53308
+ 'code[class*="language-"] *::-moz-selection': {
53309
+ background: "hsl(230, 1%, 90%)",
53310
+ color: "inherit"
53311
+ },
53312
+ 'pre[class*="language-"] *::-moz-selection': {
53313
+ background: "hsl(230, 1%, 90%)",
53314
+ color: "inherit"
53315
+ },
53316
+ 'code[class*="language-"]::selection': {
53317
+ background: "hsl(230, 1%, 90%)",
53318
+ color: "inherit"
53319
+ },
53320
+ 'code[class*="language-"] *::selection': {
53321
+ background: "hsl(230, 1%, 90%)",
53322
+ color: "inherit"
53323
+ },
53324
+ 'pre[class*="language-"] *::selection': {
53325
+ background: "hsl(230, 1%, 90%)",
53326
+ color: "inherit"
53327
+ },
53328
+ ':not(pre) > code[class*="language-"]': {
53329
+ padding: "0.2em 0.3em",
53330
+ borderRadius: "0.3em",
53331
+ whiteSpace: "normal"
53332
+ },
53333
+ comment: {
53334
+ color: "hsl(230, 4%, 64%)",
53335
+ fontStyle: "italic"
53336
+ },
53337
+ prolog: {
53338
+ color: "hsl(230, 4%, 64%)"
53339
+ },
53340
+ cdata: {
53341
+ color: "hsl(230, 4%, 64%)"
53342
+ },
53343
+ doctype: {
53344
+ color: "hsl(230, 8%, 24%)"
53345
+ },
53346
+ punctuation: {
53347
+ color: "hsl(230, 8%, 24%)"
53348
+ },
53349
+ entity: {
53350
+ color: "hsl(230, 8%, 24%)",
53351
+ cursor: "help"
53352
+ },
53353
+ "attr-name": {
53354
+ color: "hsl(35, 99%, 36%)"
53355
+ },
53356
+ "class-name": {
53357
+ color: "hsl(35, 99%, 36%)"
53358
+ },
53359
+ boolean: {
53360
+ color: "hsl(35, 99%, 36%)"
53361
+ },
53362
+ constant: {
53363
+ color: "hsl(35, 99%, 36%)"
53364
+ },
53365
+ number: {
53366
+ color: "hsl(35, 99%, 36%)"
53367
+ },
53368
+ atrule: {
53369
+ color: "hsl(35, 99%, 36%)"
53370
+ },
53371
+ keyword: {
53372
+ color: "hsl(301, 63%, 40%)"
53373
+ },
53374
+ property: {
53375
+ color: "hsl(5, 74%, 59%)"
53376
+ },
53377
+ tag: {
53378
+ color: "hsl(5, 74%, 59%)"
53379
+ },
53380
+ symbol: {
53381
+ color: "hsl(5, 74%, 59%)"
53382
+ },
53383
+ deleted: {
53384
+ color: "hsl(5, 74%, 59%)"
53385
+ },
53386
+ important: {
53387
+ color: "hsl(5, 74%, 59%)"
53388
+ },
53389
+ selector: {
53390
+ color: "hsl(119, 34%, 47%)"
53391
+ },
53392
+ string: {
53393
+ color: "hsl(119, 34%, 47%)"
53394
+ },
53395
+ char: {
53396
+ color: "hsl(119, 34%, 47%)"
53397
+ },
53398
+ builtin: {
53399
+ color: "hsl(119, 34%, 47%)"
53400
+ },
53401
+ inserted: {
53402
+ color: "hsl(119, 34%, 47%)"
53403
+ },
53404
+ regex: {
53405
+ color: "hsl(119, 34%, 47%)"
53406
+ },
53407
+ "attr-value": {
53408
+ color: "hsl(119, 34%, 47%)"
53409
+ },
53410
+ "attr-value > .token.punctuation": {
53411
+ color: "hsl(119, 34%, 47%)"
53412
+ },
53413
+ variable: {
53414
+ color: "hsl(221, 87%, 60%)"
53415
+ },
53416
+ operator: {
53417
+ color: "hsl(221, 87%, 60%)"
53418
+ },
53419
+ function: {
53420
+ color: "hsl(221, 87%, 60%)"
53421
+ },
53422
+ url: {
53423
+ color: "hsl(198, 99%, 37%)"
53424
+ },
53425
+ "attr-value > .token.punctuation.attr-equals": {
53426
+ color: "hsl(230, 8%, 24%)"
53427
+ },
53428
+ "special-attr > .token.attr-value > .token.value.css": {
53429
+ color: "hsl(230, 8%, 24%)"
53430
+ },
53431
+ ".language-css .token.selector": {
53432
+ color: "hsl(5, 74%, 59%)"
53433
+ },
53434
+ ".language-css .token.property": {
53435
+ color: "hsl(230, 8%, 24%)"
53436
+ },
53437
+ ".language-css .token.function": {
53438
+ color: "hsl(198, 99%, 37%)"
53439
+ },
53440
+ ".language-css .token.url > .token.function": {
53441
+ color: "hsl(198, 99%, 37%)"
53442
+ },
53443
+ ".language-css .token.url > .token.string.url": {
53444
+ color: "hsl(119, 34%, 47%)"
53445
+ },
53446
+ ".language-css .token.important": {
53447
+ color: "hsl(301, 63%, 40%)"
53448
+ },
53449
+ ".language-css .token.atrule .token.rule": {
53450
+ color: "hsl(301, 63%, 40%)"
53451
+ },
53452
+ ".language-javascript .token.operator": {
53453
+ color: "hsl(301, 63%, 40%)"
53454
+ },
53455
+ ".language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation": {
53456
+ color: "hsl(344, 84%, 43%)"
53457
+ },
53458
+ ".language-json .token.operator": {
53459
+ color: "hsl(230, 8%, 24%)"
53460
+ },
53461
+ ".language-json .token.null.keyword": {
53462
+ color: "hsl(35, 99%, 36%)"
53463
+ },
53464
+ ".language-markdown .token.url": {
53465
+ color: "hsl(230, 8%, 24%)"
53466
+ },
53467
+ ".language-markdown .token.url > .token.operator": {
53468
+ color: "hsl(230, 8%, 24%)"
53469
+ },
53470
+ ".language-markdown .token.url-reference.url > .token.string": {
53471
+ color: "hsl(230, 8%, 24%)"
53472
+ },
53473
+ ".language-markdown .token.url > .token.content": {
53474
+ color: "hsl(221, 87%, 60%)"
53475
+ },
53476
+ ".language-markdown .token.url > .token.url": {
53477
+ color: "hsl(198, 99%, 37%)"
53478
+ },
53479
+ ".language-markdown .token.url-reference.url": {
53480
+ color: "hsl(198, 99%, 37%)"
53481
+ },
53482
+ ".language-markdown .token.blockquote.punctuation": {
53483
+ color: "hsl(230, 4%, 64%)",
53484
+ fontStyle: "italic"
53485
+ },
53486
+ ".language-markdown .token.hr.punctuation": {
53487
+ color: "hsl(230, 4%, 64%)",
53488
+ fontStyle: "italic"
53489
+ },
53490
+ ".language-markdown .token.code-snippet": {
53491
+ color: "hsl(119, 34%, 47%)"
53492
+ },
53493
+ ".language-markdown .token.bold .token.content": {
53494
+ color: "hsl(35, 99%, 36%)"
53495
+ },
53496
+ ".language-markdown .token.italic .token.content": {
53497
+ color: "hsl(301, 63%, 40%)"
53498
+ },
53499
+ ".language-markdown .token.strike .token.content": {
53500
+ color: "hsl(5, 74%, 59%)"
53501
+ },
53502
+ ".language-markdown .token.strike .token.punctuation": {
53503
+ color: "hsl(5, 74%, 59%)"
53504
+ },
53505
+ ".language-markdown .token.list.punctuation": {
53506
+ color: "hsl(5, 74%, 59%)"
53507
+ },
53508
+ ".language-markdown .token.title.important > .token.punctuation": {
53509
+ color: "hsl(5, 74%, 59%)"
53510
+ },
53511
+ bold: {
53512
+ fontWeight: "bold"
53513
+ },
53514
+ italic: {
53515
+ fontStyle: "italic"
53516
+ },
53517
+ namespace: {
53518
+ Opacity: "0.8"
53519
+ },
53520
+ "token.tab:not(:empty):before": {
53521
+ color: "hsla(230, 8%, 24%, 0.2)"
53522
+ },
53523
+ "token.cr:before": {
53524
+ color: "hsla(230, 8%, 24%, 0.2)"
53525
+ },
53526
+ "token.lf:before": {
53527
+ color: "hsla(230, 8%, 24%, 0.2)"
53528
+ },
53529
+ "token.space:before": {
53530
+ color: "hsla(230, 8%, 24%, 0.2)"
53531
+ },
53532
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item": {
53533
+ marginRight: "0.4em"
53534
+ },
53535
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > button": {
53536
+ background: "hsl(230, 1%, 90%)",
53537
+ color: "hsl(230, 6%, 44%)",
53538
+ padding: "0.1em 0.4em",
53539
+ borderRadius: "0.3em"
53540
+ },
53541
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > a": {
53542
+ background: "hsl(230, 1%, 90%)",
53543
+ color: "hsl(230, 6%, 44%)",
53544
+ padding: "0.1em 0.4em",
53545
+ borderRadius: "0.3em"
53546
+ },
53547
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > span": {
53548
+ background: "hsl(230, 1%, 90%)",
53549
+ color: "hsl(230, 6%, 44%)",
53550
+ padding: "0.1em 0.4em",
53551
+ borderRadius: "0.3em"
53552
+ },
53553
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover": {
53554
+ background: "hsl(230, 1%, 78%)",
53555
+ color: "hsl(230, 8%, 24%)"
53556
+ },
53557
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus": {
53558
+ background: "hsl(230, 1%, 78%)",
53559
+ color: "hsl(230, 8%, 24%)"
53560
+ },
53561
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover": {
53562
+ background: "hsl(230, 1%, 78%)",
53563
+ color: "hsl(230, 8%, 24%)"
53564
+ },
53565
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus": {
53566
+ background: "hsl(230, 1%, 78%)",
53567
+ color: "hsl(230, 8%, 24%)"
53568
+ },
53569
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover": {
53570
+ background: "hsl(230, 1%, 78%)",
53571
+ color: "hsl(230, 8%, 24%)"
53572
+ },
53573
+ "div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus": {
53574
+ background: "hsl(230, 1%, 78%)",
53575
+ color: "hsl(230, 8%, 24%)"
53576
+ },
53577
+ ".line-highlight.line-highlight": {
53578
+ background: "hsla(230, 8%, 24%, 0.05)"
53579
+ },
53580
+ ".line-highlight.line-highlight:before": {
53581
+ background: "hsl(230, 1%, 90%)",
53582
+ color: "hsl(230, 8%, 24%)",
53583
+ padding: "0.1em 0.6em",
53584
+ borderRadius: "0.3em",
53585
+ boxShadow: "0 2px 0 0 rgba(0, 0, 0, 0.2)"
53586
+ },
53587
+ ".line-highlight.line-highlight[data-end]:after": {
53588
+ background: "hsl(230, 1%, 90%)",
53589
+ color: "hsl(230, 8%, 24%)",
53590
+ padding: "0.1em 0.6em",
53591
+ borderRadius: "0.3em",
53592
+ boxShadow: "0 2px 0 0 rgba(0, 0, 0, 0.2)"
53593
+ },
53594
+ "pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before": {
53595
+ backgroundColor: "hsla(230, 8%, 24%, 0.05)"
53596
+ },
53597
+ ".line-numbers.line-numbers .line-numbers-rows": {
53598
+ borderRightColor: "hsla(230, 8%, 24%, 0.2)"
53599
+ },
53600
+ ".command-line .command-line-prompt": {
53601
+ borderRightColor: "hsla(230, 8%, 24%, 0.2)"
53602
+ },
53603
+ ".line-numbers .line-numbers-rows > span:before": {
53604
+ color: "hsl(230, 1%, 62%)"
53605
+ },
53606
+ ".command-line .command-line-prompt > span:before": {
53607
+ color: "hsl(230, 1%, 62%)"
53608
+ },
53609
+ ".rainbow-braces .token.token.punctuation.brace-level-1": {
53610
+ color: "hsl(5, 74%, 59%)"
53611
+ },
53612
+ ".rainbow-braces .token.token.punctuation.brace-level-5": {
53613
+ color: "hsl(5, 74%, 59%)"
53614
+ },
53615
+ ".rainbow-braces .token.token.punctuation.brace-level-9": {
53616
+ color: "hsl(5, 74%, 59%)"
53617
+ },
53618
+ ".rainbow-braces .token.token.punctuation.brace-level-2": {
53619
+ color: "hsl(119, 34%, 47%)"
53620
+ },
53621
+ ".rainbow-braces .token.token.punctuation.brace-level-6": {
53622
+ color: "hsl(119, 34%, 47%)"
53623
+ },
53624
+ ".rainbow-braces .token.token.punctuation.brace-level-10": {
53625
+ color: "hsl(119, 34%, 47%)"
53626
+ },
53627
+ ".rainbow-braces .token.token.punctuation.brace-level-3": {
53628
+ color: "hsl(221, 87%, 60%)"
53629
+ },
53630
+ ".rainbow-braces .token.token.punctuation.brace-level-7": {
53631
+ color: "hsl(221, 87%, 60%)"
53632
+ },
53633
+ ".rainbow-braces .token.token.punctuation.brace-level-11": {
53634
+ color: "hsl(221, 87%, 60%)"
53635
+ },
53636
+ ".rainbow-braces .token.token.punctuation.brace-level-4": {
53637
+ color: "hsl(301, 63%, 40%)"
53638
+ },
53639
+ ".rainbow-braces .token.token.punctuation.brace-level-8": {
53640
+ color: "hsl(301, 63%, 40%)"
53641
+ },
53642
+ ".rainbow-braces .token.token.punctuation.brace-level-12": {
53643
+ color: "hsl(301, 63%, 40%)"
53644
+ },
53645
+ "pre.diff-highlight > code .token.token.deleted:not(.prefix)": {
53646
+ backgroundColor: "hsla(353, 100%, 66%, 0.15)"
53647
+ },
53648
+ "pre > code.diff-highlight .token.token.deleted:not(.prefix)": {
53649
+ backgroundColor: "hsla(353, 100%, 66%, 0.15)"
53650
+ },
53651
+ "pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection": {
53652
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53653
+ },
53654
+ "pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection": {
53655
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53656
+ },
53657
+ "pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection": {
53658
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53659
+ },
53660
+ "pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection": {
53661
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53662
+ },
53663
+ "pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection": {
53664
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53665
+ },
53666
+ "pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection": {
53667
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53668
+ },
53669
+ "pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection": {
53670
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53671
+ },
53672
+ "pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection": {
53673
+ backgroundColor: "hsla(353, 95%, 66%, 0.25)"
53674
+ },
53675
+ "pre.diff-highlight > code .token.token.inserted:not(.prefix)": {
53676
+ backgroundColor: "hsla(137, 100%, 55%, 0.15)"
53677
+ },
53678
+ "pre > code.diff-highlight .token.token.inserted:not(.prefix)": {
53679
+ backgroundColor: "hsla(137, 100%, 55%, 0.15)"
53680
+ },
53681
+ "pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection": {
53682
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53683
+ },
53684
+ "pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection": {
53685
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53686
+ },
53687
+ "pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection": {
53688
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53689
+ },
53690
+ "pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection": {
53691
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53692
+ },
53693
+ "pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection": {
53694
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53695
+ },
53696
+ "pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection": {
53697
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53698
+ },
53699
+ "pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection": {
53700
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53701
+ },
53702
+ "pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection": {
53703
+ backgroundColor: "hsla(135, 73%, 55%, 0.25)"
53704
+ },
53705
+ ".prism-previewer.prism-previewer:before": {
53706
+ borderColor: "hsl(0, 0, 95%)"
53707
+ },
53708
+ ".prism-previewer-gradient.prism-previewer-gradient div": {
53709
+ borderColor: "hsl(0, 0, 95%)",
53710
+ borderRadius: "0.3em"
53711
+ },
53712
+ ".prism-previewer-color.prism-previewer-color:before": {
53713
+ borderRadius: "0.3em"
53714
+ },
53715
+ ".prism-previewer-easing.prism-previewer-easing:before": {
53716
+ borderRadius: "0.3em"
53717
+ },
53718
+ ".prism-previewer.prism-previewer:after": {
53719
+ borderTopColor: "hsl(0, 0, 95%)"
53720
+ },
53721
+ ".prism-previewer-flipped.prism-previewer-flipped.after": {
53722
+ borderBottomColor: "hsl(0, 0, 95%)"
53723
+ },
53724
+ ".prism-previewer-angle.prism-previewer-angle:before": {
53725
+ background: "hsl(0, 0%, 100%)"
53726
+ },
53727
+ ".prism-previewer-time.prism-previewer-time:before": {
53728
+ background: "hsl(0, 0%, 100%)"
53729
+ },
53730
+ ".prism-previewer-easing.prism-previewer-easing": {
53731
+ background: "hsl(0, 0%, 100%)"
53732
+ },
53733
+ ".prism-previewer-angle.prism-previewer-angle circle": {
53734
+ stroke: "hsl(230, 8%, 24%)",
53735
+ strokeOpacity: "1"
53736
+ },
53737
+ ".prism-previewer-time.prism-previewer-time circle": {
53738
+ stroke: "hsl(230, 8%, 24%)",
53739
+ strokeOpacity: "1"
53740
+ },
53741
+ ".prism-previewer-easing.prism-previewer-easing circle": {
53742
+ stroke: "hsl(230, 8%, 24%)",
53743
+ fill: "transparent"
53744
+ },
53745
+ ".prism-previewer-easing.prism-previewer-easing path": {
53746
+ stroke: "hsl(230, 8%, 24%)"
53747
+ },
53748
+ ".prism-previewer-easing.prism-previewer-easing line": {
53749
+ stroke: "hsl(230, 8%, 24%)"
53750
+ }
53751
+ };
53752
+ // src/frontend/hooks/useTheme.ts
53753
+ var import_react7 = __toESM(require_react(), 1);
53754
+ function getSystemTheme() {
53755
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
53756
+ }
53757
+ function resolveTheme(setting) {
53758
+ if (setting === "system")
53759
+ return getSystemTheme();
53760
+ return setting;
53761
+ }
53762
+ function useTheme() {
53763
+ const [setting, setSetting] = import_react7.useState(() => {
53764
+ const stored = localStorage.getItem("klovi-theme");
53765
+ if (stored === "light" || stored === "dark")
53766
+ return stored;
53767
+ return "system";
53768
+ });
53769
+ const [resolved, setResolved] = import_react7.useState(() => resolveTheme(setting));
53770
+ import_react7.useEffect(() => {
53771
+ document.documentElement.setAttribute("data-theme", resolved);
53772
+ }, [resolved]);
53773
+ import_react7.useEffect(() => {
53774
+ localStorage.setItem("klovi-theme", setting);
53775
+ setResolved(resolveTheme(setting));
53776
+ }, [setting]);
53777
+ import_react7.useEffect(() => {
53778
+ if (setting !== "system")
53779
+ return;
53780
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
53781
+ const handler = () => setResolved(getSystemTheme());
53782
+ mq.addEventListener("change", handler);
53783
+ return () => mq.removeEventListener("change", handler);
53784
+ }, [setting]);
53785
+ const cycle = import_react7.useCallback(() => {
53786
+ setSetting((s2) => {
53787
+ if (s2 === "system")
53788
+ return "light";
53789
+ if (s2 === "light")
53790
+ return "dark";
53791
+ return "system";
53792
+ });
53793
+ }, []);
53794
+ return { setting, resolved, cycle };
53795
+ }
53796
+ function useFontSize() {
53797
+ const [size, setSize] = import_react7.useState(() => {
53798
+ const stored = localStorage.getItem("klovi-font-size");
53799
+ return stored ? parseInt(stored, 10) : 15;
53800
+ });
53801
+ import_react7.useEffect(() => {
53802
+ document.documentElement.style.setProperty("--font-size-base", `${size}px`);
53803
+ localStorage.setItem("klovi-font-size", String(size));
53804
+ }, [size]);
53805
+ const increase = import_react7.useCallback(() => setSize((s2) => Math.min(s2 + 2, 28)), []);
53806
+ const decrease = import_react7.useCallback(() => setSize((s2) => Math.max(s2 - 2, 10)), []);
53807
+ return { size, increase, decrease };
53808
+ }
53809
+
53810
+ // src/frontend/components/ui/CodeBlock.tsx
53811
+ var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
53812
+ function CodeBlock({ language, children }) {
53813
+ const { resolved: theme } = useTheme();
53814
+ const lang = language || "text";
53815
+ const style2 = theme === "dark" ? one_dark_default : one_light_default;
53816
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("div", {
53817
+ className: "code-block-wrapper",
53818
+ children: [
53819
+ language && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("div", {
53820
+ className: "code-block-header",
53821
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("span", {
53822
+ children: language
53823
+ }, undefined, false, undefined, this)
53824
+ }, undefined, false, undefined, this),
53825
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV("div", {
53826
+ className: "code-block-content",
53827
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(prism_default2, {
53828
+ language: lang,
53829
+ style: style2,
53830
+ customStyle: {
53831
+ margin: 0,
53832
+ fontSize: "0.85em",
53833
+ lineHeight: 1.5
53834
+ },
53835
+ showLineNumbers: children.split(`
53836
+ `).length > 3,
53837
+ children: children.replace(/\n$/, "")
53838
+ }, undefined, false, undefined, this)
53839
+ }, undefined, false, undefined, this)
53840
+ ]
53841
+ }, undefined, true, undefined, this);
53842
+ }
53843
+
53844
+ // src/frontend/components/ui/MarkdownRenderer.tsx
53845
+ var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
53846
+ var FILE_REF_RE = /@([\w./-]+\.\w+)/g;
53847
+ function renderTextWithFileRefs(text7) {
53848
+ const parts = [];
53849
+ let last = 0;
53850
+ FILE_REF_RE.lastIndex = 0;
53851
+ let match = FILE_REF_RE.exec(text7);
53852
+ while (match !== null) {
53853
+ if (match.index > last) {
53854
+ parts.push(text7.slice(last, match.index));
53855
+ }
53856
+ parts.push(/* @__PURE__ */ jsx_dev_runtime7.jsxDEV("code", {
53857
+ className: "file-ref",
53858
+ children: [
53859
+ "@",
53860
+ match[1]
53861
+ ]
53862
+ }, match.index, true, undefined, this));
53863
+ last = FILE_REF_RE.lastIndex;
53864
+ match = FILE_REF_RE.exec(text7);
53865
+ }
53866
+ if (parts.length === 0)
53867
+ return text7;
53868
+ if (last < text7.length)
53869
+ parts.push(text7.slice(last));
53870
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
53871
+ children: parts
53872
+ }, undefined, false, undefined, this);
53873
+ }
53874
+ function MarkdownRenderer({ content: content3 }) {
53875
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("div", {
53876
+ className: "markdown-content",
53877
+ children: /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Markdown, {
53878
+ remarkPlugins: [remarkGfm],
53879
+ components: {
53880
+ p({ children }) {
53881
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("p", {
53882
+ children: import_react8.default.Children.map(children, (child) => typeof child === "string" ? renderTextWithFileRefs(child) : child)
53883
+ }, undefined, false, undefined, this);
53884
+ },
53885
+ code({ className, children, ...props }) {
53886
+ const match = /language-(\w+)/.exec(className || "");
53887
+ const text7 = String(children).replace(/\n$/, "");
53888
+ if (!match && !text7.includes(`
53889
+ `)) {
53890
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("code", {
53891
+ className,
53892
+ ...props,
53893
+ children
53894
+ }, undefined, false, undefined, this);
53895
+ }
53896
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(CodeBlock, {
53897
+ language: match?.[1],
53898
+ children: text7
53899
+ }, undefined, false, undefined, this);
53900
+ },
53901
+ a({ href, children, ...props }) {
53902
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV("a", {
53903
+ href,
53904
+ target: "_blank",
53905
+ rel: "noopener noreferrer",
53906
+ ...props,
53907
+ children
53908
+ }, undefined, false, undefined, this);
53909
+ }
53910
+ },
53911
+ children: content3
53912
+ }, undefined, false, undefined, this)
53913
+ }, undefined, false, undefined, this);
53914
+ }
53915
+
53916
+ // src/shared/content-blocks.ts
53917
+ function groupContentBlocks(blocks) {
53918
+ const groups = [];
53919
+ let nonTextGroup = [];
53920
+ for (const block of blocks) {
53921
+ if (block.type === "text") {
53922
+ if (nonTextGroup.length > 0) {
53923
+ groups.push(nonTextGroup);
53924
+ nonTextGroup = [];
53925
+ }
53926
+ groups.push([block]);
53927
+ } else {
53928
+ nonTextGroup.push(block);
53929
+ }
53930
+ }
53931
+ if (nonTextGroup.length > 0) {
53932
+ groups.push(nonTextGroup);
53933
+ }
53934
+ return groups;
53935
+ }
53936
+
53937
+ // src/frontend/utils/model.ts
53938
+ function shortModel(model) {
53939
+ const match = model.match(/claude-(opus|sonnet|haiku)-(\d+)(?:-(\d{1,2}))?(?:-\d{8,})?$/);
53940
+ if (match) {
53941
+ const family = match[1].charAt(0).toUpperCase() + match[1].slice(1);
53942
+ const major = match[2];
53943
+ const minor = match[3];
53944
+ return minor ? `${family} ${major}.${minor}` : `${family} ${major}`;
53945
+ }
53946
+ return model;
53947
+ }
53948
+
53949
+ // src/frontend/components/ui/CollapsibleSection.tsx
53950
+ var import_react9 = __toESM(require_react(), 1);
53951
+ var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
53952
+ function CollapsibleSection({
53953
+ title,
53954
+ defaultOpen = false,
53955
+ children
53956
+ }) {
53957
+ const [open, setOpen] = import_react9.useState(defaultOpen);
53958
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53959
+ className: "collapsible",
53960
+ children: [
53961
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("button", {
53962
+ type: "button",
53963
+ className: "collapsible-header",
53964
+ onClick: () => setOpen(!open),
53965
+ children: [
53966
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("span", {
53967
+ className: `collapsible-chevron ${open ? "open" : ""}`,
53968
+ children: "▶"
53969
+ }, undefined, false, undefined, this),
53970
+ title
53971
+ ]
53972
+ }, undefined, true, undefined, this),
53973
+ open && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53974
+ className: "collapsible-content",
53975
+ children
53976
+ }, undefined, false, undefined, this)
53977
+ ]
53978
+ }, undefined, true, undefined, this);
53979
+ }
53980
+
53981
+ // src/frontend/components/ui/DiffView.tsx
53982
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
53983
+ function formatDiff(oldString, newString) {
53984
+ const lines = [];
53985
+ if (oldString !== "") {
53986
+ for (const line of oldString.split(`
53987
+ `)) {
53988
+ lines.push(`-${line}`);
53989
+ }
53990
+ }
53991
+ if (newString !== "") {
53992
+ for (const line of newString.split(`
53993
+ `)) {
53994
+ lines.push(`+${line}`);
53995
+ }
53996
+ }
53997
+ return lines.join(`
53998
+ `);
53999
+ }
54000
+ function DiffView({ filePath, oldString, newString }) {
54001
+ const { resolved: theme } = useTheme();
54002
+ const diff2 = formatDiff(oldString, newString);
54003
+ const style2 = theme === "dark" ? one_dark_default : one_light_default;
54004
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("div", {
54005
+ className: "diff-view-wrapper",
54006
+ children: [
54007
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("div", {
54008
+ className: "diff-view-header",
54009
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("span", {
54010
+ children: filePath
54011
+ }, undefined, false, undefined, this)
54012
+ }, undefined, false, undefined, this),
54013
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("div", {
54014
+ className: "diff-view-content",
54015
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(prism_default2, {
54016
+ language: "diff",
54017
+ style: style2,
54018
+ customStyle: {
54019
+ margin: 0,
54020
+ fontSize: "0.85em",
54021
+ lineHeight: 1.5
54022
+ },
54023
+ showLineNumbers: diff2.split(`
54024
+ `).length > 3,
54025
+ children: diff2
54026
+ }, undefined, false, undefined, this)
54027
+ }, undefined, false, undefined, this)
54028
+ ]
54029
+ }, undefined, true, undefined, this);
54030
+ }
54031
+
54032
+ // src/frontend/utils/format-detector.ts
54033
+ function detectOutputFormat(output) {
54034
+ const trimmed = output.trim();
54035
+ if (!trimmed)
54036
+ return null;
54037
+ if (isJson(trimmed))
54038
+ return "json";
54039
+ if (isDiff(trimmed))
54040
+ return "diff";
54041
+ if (isXmlOrHtml(trimmed))
54042
+ return "markup";
54043
+ if (isTypeScript(trimmed))
54044
+ return "typescript";
54045
+ if (isPython(trimmed))
54046
+ return "python";
54047
+ if (isCss(trimmed))
54048
+ return "css";
54049
+ if (isYaml(trimmed))
54050
+ return "yaml";
54051
+ return null;
54052
+ }
54053
+ function isJson(text7) {
54054
+ const first = text7[0];
54055
+ const last = text7[text7.length - 1];
54056
+ if (!(first === "{" && last === "}" || first === "[" && last === "]"))
54057
+ return false;
54058
+ try {
54059
+ JSON.parse(text7);
54060
+ return true;
54061
+ } catch {
54062
+ return false;
54063
+ }
54064
+ }
54065
+ function isDiff(text7) {
54066
+ if (text7.startsWith("diff ") || text7.startsWith("--- ") || text7.startsWith("+++ "))
54067
+ return true;
54068
+ const lines = text7.split(`
54069
+ `);
54070
+ let diffLineCount = 0;
54071
+ for (const line of lines) {
54072
+ if (line.startsWith("+") || line.startsWith("-") || line.startsWith("@@")) {
54073
+ diffLineCount++;
54074
+ }
54075
+ }
54076
+ return diffLineCount >= 3 && diffLineCount / lines.length > 0.3;
54077
+ }
54078
+ function isXmlOrHtml(text7) {
54079
+ if (text7.startsWith("<?xml") || text7.startsWith("<!DOCTYPE") || text7.startsWith("<!doctype")) {
54080
+ return true;
54081
+ }
54082
+ if (text7.startsWith("<") && /<\/\w+>\s*$/.test(text7))
54083
+ return true;
54084
+ return false;
54085
+ }
54086
+ function isTypeScript(text7) {
54087
+ if (/^(export\s+)?(interface|type)\s+\w+/.test(text7))
54088
+ return true;
54089
+ if (/:\s*(string|number|boolean|Record|Array)\b/.test(text7) && hasCodeStructure(text7))
54090
+ return true;
54091
+ return false;
54092
+ }
54093
+ function isPython(text7) {
54094
+ if (/^(def|class|import|from)\s+\w+/.test(text7))
54095
+ return true;
54096
+ if (text7.includes("if __name__"))
54097
+ return true;
54098
+ const lines = text7.split(`
54099
+ `);
54100
+ const pyLines = lines.filter((l) => /^\s*(def|class|if|for|while|with|try|except)\s/.test(l));
54101
+ return pyLines.length >= 2;
54102
+ }
54103
+ function isCss(text7) {
54104
+ if (/^[@.#:\w[\]-]+\s*\{/m.test(text7) && /:\s*[^;]+;/m.test(text7))
54105
+ return true;
54106
+ if (/^@(media|import|keyframes|font-face)\s/.test(text7))
54107
+ return true;
54108
+ return false;
54109
+ }
54110
+ function isYaml(text7) {
54111
+ if (text7.startsWith("---"))
54112
+ return true;
54113
+ if (text7.includes("{") || text7.includes("["))
54114
+ return false;
54115
+ const lines = text7.split(`
54116
+ `);
54117
+ const kvLines = lines.filter((l) => /^\s*[\w.-]+:\s/.test(l));
54118
+ return kvLines.length >= 2 && kvLines.length / lines.length > 0.3;
54119
+ }
54120
+ function hasCodeStructure(text7) {
54121
+ const lines = text7.split(`
54122
+ `);
54123
+ return lines.length >= 2 && /^(export|import|const|let|var|function|class)\s/.test(text7);
54124
+ }
54125
+
54126
+ // src/frontend/components/message/SmartToolOutput.tsx
54127
+ var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
54128
+ function SmartToolOutput({ output, isError, resultImages }) {
54129
+ const truncated = truncateOutput(output);
54130
+ const wasTruncated = output.length > MAX_OUTPUT_LENGTH;
54131
+ const detectedLang = truncated ? detectOutputFormat(truncated) : null;
54132
+ if (!output && (!resultImages || resultImages.length === 0))
54133
+ return null;
54134
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54135
+ children: [
54136
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54137
+ className: "tool-section-label",
54138
+ children: "Output"
54139
+ }, undefined, false, undefined, this),
54140
+ output && (detectedLang && !isError ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(CodeBlock, {
54141
+ language: detectedLang,
54142
+ children: truncated
54143
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54144
+ className: `tool-call-output ${isError ? "tool-call-error" : ""}`,
54145
+ children: truncated
54146
+ }, undefined, false, undefined, this)),
54147
+ wasTruncated && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54148
+ className: "tool-call-truncated",
54149
+ children: "... (truncated)"
54150
+ }, undefined, false, undefined, this),
54151
+ resultImages && resultImages.length > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54152
+ className: "tool-result-images",
54153
+ children: resultImages.map((img, i) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("a", {
54154
+ href: `data:${img.mediaType};base64,${img.data}`,
54155
+ target: "_blank",
54156
+ rel: "noopener noreferrer",
54157
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("img", {
54158
+ className: "tool-result-image",
54159
+ src: `data:${img.mediaType};base64,${img.data}`,
54160
+ alt: `Tool result ${i + 1}`
54161
+ }, undefined, false, undefined, this)
54162
+ }, i, false, undefined, this))
54163
+ }, undefined, false, undefined, this)
54164
+ ]
54165
+ }, undefined, true, undefined, this);
54166
+ }
54167
+
54168
+ // src/frontend/components/message/BashToolContent.tsx
54169
+ var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
54170
+ function BashToolContent({ call }) {
54171
+ const command = String(call.input.command || "");
54172
+ return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(jsx_dev_runtime11.Fragment, {
54173
+ children: [
54174
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54175
+ style: { marginBottom: 8 },
54176
+ children: [
54177
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54178
+ className: "tool-section-label",
54179
+ children: "Command"
54180
+ }, undefined, false, undefined, this),
54181
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(CodeBlock, {
54182
+ language: "bash",
54183
+ children: command
54184
+ }, undefined, false, undefined, this)
54185
+ ]
54186
+ }, undefined, true, undefined, this),
54187
+ /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(SmartToolOutput, {
54188
+ output: call.result,
54189
+ isError: call.isError,
54190
+ resultImages: call.resultImages
54191
+ }, undefined, false, undefined, this)
54192
+ ]
54193
+ }, undefined, true, undefined, this);
54194
+ }
54195
+
54196
+ // src/frontend/components/message/ToolCall.tsx
54197
+ var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
54198
+ var MAX_OUTPUT_LENGTH = 5000;
54199
+ var MAX_CONTENT_LENGTH = 2000;
54200
+ var MAX_THINKING_PREVIEW = 100;
54201
+ function isEditWithDiff(call) {
54202
+ return call.name === "Edit" && typeof call.input.old_string === "string" && typeof call.input.new_string === "string";
54203
+ }
54204
+ function isJsonFallbackInput(call) {
54205
+ return !(call.name in INPUT_FORMATTERS);
54206
+ }
54207
+ function DefaultToolContent({ call }) {
54208
+ const formattedInput = formatToolInput(call);
54209
+ const jsonInput = isJsonFallbackInput(call);
54210
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(jsx_dev_runtime12.Fragment, {
54211
+ children: [
54212
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54213
+ style: { marginBottom: 8 },
54214
+ children: [
54215
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54216
+ className: "tool-section-label",
54217
+ children: "Input"
54218
+ }, undefined, false, undefined, this),
54219
+ jsonInput ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(CodeBlock, {
54220
+ language: "json",
54221
+ children: formattedInput
54222
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54223
+ className: "tool-call-input",
54224
+ children: formattedInput
54225
+ }, undefined, false, undefined, this)
54226
+ ]
54227
+ }, undefined, true, undefined, this),
54228
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(SmartToolOutput, {
54229
+ output: call.result,
54230
+ isError: call.isError,
54231
+ resultImages: call.resultImages
54232
+ }, undefined, false, undefined, this)
54233
+ ]
54234
+ }, undefined, true, undefined, this);
54235
+ }
54236
+ function ToolCall({ call, sessionId, project }) {
53279
54237
  const summary = getToolSummary(call);
53280
54238
  const mcpServer = getMcpServer(call.name);
53281
54239
  const hasSubAgent = call.name === "Task" && call.subAgentId && sessionId && project;
53282
54240
  const displayName = hasSubAgent ? "Sub-Agent" : mcpServer ? call.name.split("__").slice(1).join("__").replace(/__/g, " > ") : call.name;
53283
- return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
54241
+ return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
53284
54242
  className: "tool-call",
53285
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(CollapsibleSection, {
53286
- title: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("span", {
54243
+ children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(CollapsibleSection, {
54244
+ title: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("span", {
53287
54245
  children: [
53288
- mcpServer && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("span", {
54246
+ mcpServer && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("span", {
53289
54247
  className: "tool-mcp-server",
53290
54248
  children: mcpServer
53291
54249
  }, undefined, false, undefined, this),
53292
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("span", {
54250
+ /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("span", {
53293
54251
  className: "tool-call-name",
53294
54252
  children: displayName
53295
54253
  }, undefined, false, undefined, this),
53296
- summary && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("span", {
54254
+ summary && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("span", {
53297
54255
  className: "tool-call-summary",
53298
54256
  children: [
53299
54257
  " — ",
53300
54258
  summary
53301
54259
  ]
53302
54260
  }, undefined, true, undefined, this),
53303
- call.isError && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("span", {
54261
+ call.isError && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("span", {
53304
54262
  className: "tool-call-error",
53305
54263
  children: " (error)"
53306
54264
  }, undefined, false, undefined, this),
53307
- hasSubAgent && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("a", {
54265
+ hasSubAgent && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("a", {
53308
54266
  className: "subagent-link",
53309
54267
  href: `#/${project}/${sessionId}/subagent/${call.subAgentId}`,
53310
54268
  onClick: (e) => e.stopPropagation(),
@@ -53312,47 +54270,16 @@ function ToolCall({ call, sessionId, project }) {
53312
54270
  }, undefined, false, undefined, this)
53313
54271
  ]
53314
54272
  }, undefined, true, undefined, this),
53315
- children: [
53316
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53317
- style: { marginBottom: 8 },
53318
- children: [
53319
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53320
- className: "tool-section-label",
53321
- children: "Input"
53322
- }, undefined, false, undefined, this),
53323
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53324
- className: "tool-call-input",
53325
- children: formatToolInput(call)
53326
- }, undefined, false, undefined, this)
53327
- ]
53328
- }, undefined, true, undefined, this),
53329
- (call.result || call.resultImages && call.resultImages.length > 0) && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53330
- children: [
53331
- /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53332
- className: "tool-section-label",
53333
- children: "Output"
53334
- }, undefined, false, undefined, this),
53335
- call.result && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53336
- className: `tool-call-output ${call.isError ? "tool-call-error" : ""}`,
53337
- children: truncateOutput(call.result)
53338
- }, undefined, false, undefined, this),
53339
- call.resultImages && call.resultImages.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("div", {
53340
- className: "tool-result-images",
53341
- children: call.resultImages.map((img, i) => /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("a", {
53342
- href: `data:${img.mediaType};base64,${img.data}`,
53343
- target: "_blank",
53344
- rel: "noopener noreferrer",
53345
- children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV("img", {
53346
- className: "tool-result-image",
53347
- src: `data:${img.mediaType};base64,${img.data}`,
53348
- alt: `Tool result ${i + 1}`
53349
- }, undefined, false, undefined, this)
53350
- }, i, false, undefined, this))
53351
- }, undefined, false, undefined, this)
53352
- ]
53353
- }, undefined, true, undefined, this)
53354
- ]
53355
- }, undefined, true, undefined, this)
54273
+ children: isEditWithDiff(call) ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(DiffView, {
54274
+ filePath: String(call.input.file_path || ""),
54275
+ oldString: String(call.input.old_string),
54276
+ newString: String(call.input.new_string)
54277
+ }, undefined, false, undefined, this) : call.name === "Bash" ? /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(BashToolContent, {
54278
+ call
54279
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(DefaultToolContent, {
54280
+ call
54281
+ }, undefined, false, undefined, this)
54282
+ }, undefined, false, undefined, this)
53356
54283
  }, undefined, false, undefined, this);
53357
54284
  }
53358
54285
  function getMcpServer(name2) {
@@ -53529,34 +54456,32 @@ function truncate(s2, max) {
53529
54456
  function truncateOutput(s2) {
53530
54457
  if (s2.length <= MAX_OUTPUT_LENGTH)
53531
54458
  return s2;
53532
- return `${s2.slice(0, MAX_OUTPUT_LENGTH)}
53533
-
53534
- ... (truncated)`;
54459
+ return s2.slice(0, MAX_OUTPUT_LENGTH);
53535
54460
  }
53536
54461
 
53537
54462
  // src/frontend/components/message/ThinkingBlock.tsx
53538
- var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
54463
+ var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
53539
54464
  function ThinkingBlock({ block }) {
53540
54465
  const preview = block.text.length > MAX_THINKING_PREVIEW ? `${block.text.slice(0, MAX_THINKING_PREVIEW)}...` : block.text;
53541
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("div", {
54466
+ return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("div", {
53542
54467
  className: "thinking-block",
53543
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(CollapsibleSection, {
53544
- title: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("span", {
54468
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(CollapsibleSection, {
54469
+ title: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("span", {
53545
54470
  children: [
53546
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("span", {
54471
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("span", {
53547
54472
  style: { color: "var(--text-muted)" },
53548
54473
  children: "Thinking:"
53549
54474
  }, undefined, false, undefined, this),
53550
54475
  " ",
53551
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("span", {
54476
+ /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("span", {
53552
54477
  className: "tool-call-summary",
53553
54478
  children: preview
53554
54479
  }, undefined, false, undefined, this)
53555
54480
  ]
53556
54481
  }, undefined, true, undefined, this),
53557
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV("div", {
54482
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("div", {
53558
54483
  className: "thinking-content",
53559
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(MarkdownRenderer, {
54484
+ children: /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MarkdownRenderer, {
53560
54485
  content: block.text
53561
54486
  }, undefined, false, undefined, this)
53562
54487
  }, undefined, false, undefined, this)
@@ -53565,7 +54490,7 @@ function ThinkingBlock({ block }) {
53565
54490
  }
53566
54491
 
53567
54492
  // src/frontend/components/message/AssistantMessage.tsx
53568
- var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
54493
+ var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
53569
54494
  function AssistantMessage({
53570
54495
  turn,
53571
54496
  visibleSubSteps,
@@ -53576,19 +54501,19 @@ function AssistantMessage({
53576
54501
  const subSteps = [];
53577
54502
  for (let g = 0;g < groups.length; g++) {
53578
54503
  const group = groups[g];
53579
- subSteps.push(/* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54504
+ subSteps.push(/* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
53580
54505
  children: group.map((block, i) => {
53581
54506
  if (block.type === "thinking") {
53582
- return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ThinkingBlock, {
54507
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(ThinkingBlock, {
53583
54508
  block: block.block
53584
54509
  }, `thinking-${i}`, false, undefined, this);
53585
54510
  }
53586
54511
  if (block.type === "text") {
53587
- return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(MarkdownRenderer, {
54512
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(MarkdownRenderer, {
53588
54513
  content: block.text
53589
54514
  }, `text-${i}`, false, undefined, this);
53590
54515
  }
53591
- return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ToolCall, {
54516
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV(ToolCall, {
53592
54517
  call: block.call,
53593
54518
  sessionId,
53594
54519
  project
@@ -53598,28 +54523,28 @@ function AssistantMessage({
53598
54523
  }
53599
54524
  const limit = visibleSubSteps !== undefined ? visibleSubSteps : subSteps.length;
53600
54525
  const visible = subSteps.slice(0, limit);
53601
- return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54526
+ return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
53602
54527
  className: "message message-assistant",
53603
54528
  children: [
53604
- /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54529
+ /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
53605
54530
  className: "message-role",
53606
54531
  children: [
53607
54532
  "Assistant",
53608
- turn.model && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("span", {
54533
+ turn.model && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("span", {
53609
54534
  style: { fontWeight: 400, marginLeft: 8 },
53610
54535
  children: shortModel(turn.model)
53611
54536
  }, undefined, false, undefined, this),
53612
- turn.timestamp && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("span", {
54537
+ turn.timestamp && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("span", {
53613
54538
  className: "message-timestamp",
53614
54539
  children: formatTimestamp(turn.timestamp)
53615
54540
  }, undefined, false, undefined, this)
53616
54541
  ]
53617
54542
  }, undefined, true, undefined, this),
53618
- visible.map((node2, i) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54543
+ visible.map((node2, i) => /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
53619
54544
  className: visibleSubSteps !== undefined && i === visible.length - 1 ? "step-enter" : "",
53620
54545
  children: node2
53621
54546
  }, i, false, undefined, this)),
53622
- turn.usage && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("div", {
54547
+ turn.usage && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
53623
54548
  className: "token-usage",
53624
54549
  children: [
53625
54550
  turn.usage.inputTokens.toLocaleString(),
@@ -53627,14 +54552,14 @@ function AssistantMessage({
53627
54552
  turn.usage.outputTokens.toLocaleString(),
53628
54553
  " ",
53629
54554
  "out",
53630
- turn.usage.cacheReadTokens && turn.usage.cacheReadTokens > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("span", {
54555
+ turn.usage.cacheReadTokens && turn.usage.cacheReadTokens > 0 && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("span", {
53631
54556
  children: [
53632
54557
  " · ",
53633
54558
  turn.usage.cacheReadTokens.toLocaleString(),
53634
54559
  " cache read"
53635
54560
  ]
53636
54561
  }, undefined, true, undefined, this),
53637
- turn.usage.cacheCreationTokens && turn.usage.cacheCreationTokens > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV("span", {
54562
+ turn.usage.cacheCreationTokens && turn.usage.cacheCreationTokens > 0 && /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("span", {
53638
54563
  children: [
53639
54564
  " · ",
53640
54565
  turn.usage.cacheCreationTokens.toLocaleString(),
@@ -53648,7 +54573,7 @@ function AssistantMessage({
53648
54573
  }
53649
54574
 
53650
54575
  // src/frontend/components/message/UserMessage.tsx
53651
- var jsx_dev_runtime11 = __toESM(require_jsx_dev_runtime(), 1);
54576
+ var jsx_dev_runtime15 = __toESM(require_jsx_dev_runtime(), 1);
53652
54577
  var STATUS_RE = /^\[.+\]$/;
53653
54578
  var PLAN_PREFIX = "Implement the following plan";
53654
54579
  function UserMessage({
@@ -53660,7 +54585,7 @@ function UserMessage({
53660
54585
  }) {
53661
54586
  const isStatus = STATUS_RE.test(turn.text.trim());
53662
54587
  if (isStatus) {
53663
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54588
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
53664
54589
  className: "status-notice",
53665
54590
  children: turn.text
53666
54591
  }, undefined, false, undefined, this);
@@ -53668,42 +54593,42 @@ function UserMessage({
53668
54593
  const isPlanMessage = turn.text.startsWith(PLAN_PREFIX);
53669
54594
  const showPlanLink = planSessionId && project && isPlanMessage;
53670
54595
  const showImplLink = implSessionId && project && !isPlanMessage;
53671
- return /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54596
+ return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
53672
54597
  className: `message ${isSubAgent ? "message-root-agent" : "message-user"}`,
53673
54598
  children: [
53674
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54599
+ /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
53675
54600
  className: "message-role",
53676
54601
  children: [
53677
54602
  isSubAgent ? "Root Agent" : "User",
53678
- showPlanLink && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("a", {
54603
+ showPlanLink && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("a", {
53679
54604
  className: "subagent-link",
53680
54605
  href: `#/${project}/${planSessionId}`,
53681
54606
  children: "View planning session"
53682
54607
  }, undefined, false, undefined, this),
53683
- showImplLink && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("a", {
54608
+ showImplLink && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("a", {
53684
54609
  className: "subagent-link",
53685
54610
  href: `#/${project}/${implSessionId}`,
53686
54611
  children: "View implementation session"
53687
54612
  }, undefined, false, undefined, this),
53688
- turn.timestamp && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("span", {
54613
+ turn.timestamp && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("span", {
53689
54614
  className: "message-timestamp",
53690
54615
  children: formatTimestamp(turn.timestamp)
53691
54616
  }, undefined, false, undefined, this)
53692
54617
  ]
53693
54618
  }, undefined, true, undefined, this),
53694
- turn.command && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54619
+ turn.command && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
53695
54620
  className: "command-call",
53696
- children: /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("span", {
54621
+ children: /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("span", {
53697
54622
  className: "command-call-label",
53698
54623
  children: turn.command.name
53699
54624
  }, undefined, false, undefined, this)
53700
54625
  }, undefined, false, undefined, this),
53701
- /* @__PURE__ */ jsx_dev_runtime11.jsxDEV(MarkdownRenderer, {
54626
+ /* @__PURE__ */ jsx_dev_runtime15.jsxDEV(MarkdownRenderer, {
53702
54627
  content: turn.text
53703
54628
  }, undefined, false, undefined, this),
53704
- turn.attachments && turn.attachments.length > 0 && /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("div", {
54629
+ turn.attachments && turn.attachments.length > 0 && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
53705
54630
  className: "attachments",
53706
- children: turn.attachments.map((a, i) => /* @__PURE__ */ jsx_dev_runtime11.jsxDEV("span", {
54631
+ children: turn.attachments.map((a, i) => /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("span", {
53707
54632
  className: "attachment-badge",
53708
54633
  children: [
53709
54634
  "image/",
@@ -53716,50 +54641,88 @@ function UserMessage({
53716
54641
  }
53717
54642
 
53718
54643
  // src/frontend/components/message/MessageList.tsx
53719
- var jsx_dev_runtime12 = __toESM(require_jsx_dev_runtime(), 1);
54644
+ var jsx_dev_runtime16 = __toESM(require_jsx_dev_runtime(), 1);
53720
54645
  function renderTurn(turn, index2, isActive, visibleSubSteps, sessionId, project, isSubAgent, planSessionId, implSessionId) {
53721
54646
  const activeClass = isActive ? "active-message" : "";
53722
54647
  switch (turn.kind) {
53723
54648
  case "user":
53724
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54649
+ return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
53725
54650
  className: isActive ? "active-message step-enter" : "",
53726
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(UserMessage, {
54651
+ children: /* @__PURE__ */ jsx_dev_runtime16.jsxDEV(UserMessage, {
53727
54652
  turn,
53728
54653
  isSubAgent,
53729
54654
  planSessionId,
53730
54655
  implSessionId,
53731
54656
  project
53732
54657
  }, undefined, false, undefined, this)
53733
- }, turn.uuid || index2, false, undefined, this);
54658
+ }, undefined, false, undefined, this);
53734
54659
  case "assistant":
53735
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54660
+ return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
53736
54661
  className: activeClass,
53737
- children: /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(AssistantMessage, {
54662
+ children: /* @__PURE__ */ jsx_dev_runtime16.jsxDEV(AssistantMessage, {
53738
54663
  turn,
53739
54664
  visibleSubSteps: visibleSubSteps?.get(index2),
53740
54665
  sessionId,
53741
54666
  project
53742
54667
  }, undefined, false, undefined, this)
53743
- }, turn.uuid || index2, false, undefined, this);
54668
+ }, undefined, false, undefined, this);
53744
54669
  case "system":
53745
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54670
+ return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
53746
54671
  className: `message message-system ${activeClass}`,
53747
54672
  children: [
53748
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54673
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
53749
54674
  className: "message-role",
53750
54675
  children: [
53751
54676
  "System",
53752
- turn.timestamp && /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("span", {
54677
+ turn.timestamp && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("span", {
53753
54678
  className: "message-timestamp",
53754
54679
  children: formatTimestamp(turn.timestamp)
53755
54680
  }, undefined, false, undefined, this)
53756
54681
  ]
53757
54682
  }, undefined, true, undefined, this),
53758
- /* @__PURE__ */ jsx_dev_runtime12.jsxDEV(MarkdownRenderer, {
54683
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV(MarkdownRenderer, {
53759
54684
  content: turn.text
53760
54685
  }, undefined, false, undefined, this)
53761
54686
  ]
53762
- }, turn.uuid || index2, true, undefined, this);
54687
+ }, undefined, true, undefined, this);
54688
+ case "parse_error":
54689
+ return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
54690
+ className: `message message-parse-error ${activeClass}`,
54691
+ children: [
54692
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
54693
+ className: "message-role",
54694
+ children: [
54695
+ "Parse Error",
54696
+ turn.lineNumber > 0 && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("span", {
54697
+ className: "parse-error-line",
54698
+ children: [
54699
+ "line ",
54700
+ turn.lineNumber
54701
+ ]
54702
+ }, undefined, true, undefined, this)
54703
+ ]
54704
+ }, undefined, true, undefined, this),
54705
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
54706
+ className: "parse-error-type",
54707
+ children: turn.errorType === "json_parse" ? "Invalid JSON" : "Invalid Structure"
54708
+ }, undefined, false, undefined, this),
54709
+ turn.errorDetails && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
54710
+ className: "parse-error-details",
54711
+ children: turn.errorDetails
54712
+ }, undefined, false, undefined, this),
54713
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("details", {
54714
+ className: "parse-error-raw",
54715
+ children: [
54716
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("summary", {
54717
+ children: "Raw content"
54718
+ }, undefined, false, undefined, this),
54719
+ /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("pre", {
54720
+ children: turn.rawLine
54721
+ }, undefined, false, undefined, this)
54722
+ ]
54723
+ }, undefined, true, undefined, this)
54724
+ ]
54725
+ }, undefined, true, undefined, this);
53763
54726
  default:
53764
54727
  return null;
53765
54728
  }
@@ -53779,66 +54742,60 @@ function MessageList({
53779
54742
  return false;
53780
54743
  return !STATUS_RE2.test(t.text.trim());
53781
54744
  });
53782
- return /* @__PURE__ */ jsx_dev_runtime12.jsxDEV("div", {
54745
+ return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
53783
54746
  className: "message-list",
53784
54747
  children: turns.map((turn, index2) => {
53785
54748
  const isActive = visibleSubSteps ? index2 === turns.length - 1 : false;
53786
- return renderTurn(turn, index2, isActive, visibleSubSteps, sessionId, project, isSubAgent, planSessionId, index2 === firstUserTurnIndex ? implSessionId : undefined);
54749
+ return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV(ErrorBoundary, {
54750
+ inline: true,
54751
+ children: renderTurn(turn, index2, isActive, visibleSubSteps, sessionId, project, isSubAgent, planSessionId, index2 === firstUserTurnIndex ? implSessionId : undefined)
54752
+ }, turn.uuid || index2, false, undefined, this);
53787
54753
  })
53788
54754
  }, undefined, false, undefined, this);
53789
54755
  }
53790
54756
 
53791
54757
  // src/frontend/components/message/SubAgentView.tsx
53792
- var jsx_dev_runtime13 = __toESM(require_jsx_dev_runtime(), 1);
54758
+ var jsx_dev_runtime17 = __toESM(require_jsx_dev_runtime(), 1);
53793
54759
  function SubAgentView({ sessionId, project, agentId }) {
53794
- const [session, setSession] = import_react9.useState(null);
53795
- const [loading, setLoading] = import_react9.useState(true);
53796
- const [error, setError] = import_react9.useState(null);
53797
- import_react9.useEffect(() => {
53798
- setLoading(true);
53799
- setError(null);
53800
- fetch(`/api/sessions/${sessionId}/subagents/${agentId}?project=${encodeURIComponent(project)}`).then((r2) => {
53801
- if (!r2.ok)
53802
- throw new Error(`HTTP ${r2.status}`);
53803
- return r2.json();
53804
- }).then((data) => {
53805
- setSession(data.session);
53806
- setLoading(false);
53807
- }).catch((e) => {
53808
- setError(e.message);
53809
- setLoading(false);
53810
- });
53811
- }, [sessionId, project, agentId]);
54760
+ const { data, loading, error, retry } = useFetch(`/api/sessions/${sessionId}/subagents/${agentId}?project=${encodeURIComponent(project)}`, [sessionId, project, agentId]);
53812
54761
  if (loading)
53813
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("div", {
54762
+ return /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
53814
54763
  className: "loading",
53815
54764
  children: "Loading sub-agent conversation..."
53816
54765
  }, undefined, false, undefined, this);
53817
- if (error)
53818
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("div", {
53819
- className: "loading",
53820
- style: { color: "var(--error)" },
54766
+ if (error) {
54767
+ return /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
54768
+ className: "fetch-error",
53821
54769
  children: [
53822
- "Error: ",
53823
- error
54770
+ /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("span", {
54771
+ className: "fetch-error-message",
54772
+ children: [
54773
+ "Error: ",
54774
+ error
54775
+ ]
54776
+ }, undefined, true, undefined, this),
54777
+ /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("button", {
54778
+ type: "button",
54779
+ className: "btn btn-sm",
54780
+ onClick: retry,
54781
+ children: "Retry"
54782
+ }, undefined, false, undefined, this)
53824
54783
  ]
53825
54784
  }, undefined, true, undefined, this);
53826
- if (!session || session.turns.length === 0)
53827
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV("div", {
54785
+ }
54786
+ if (!data?.session || data.session.turns.length === 0)
54787
+ return /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
53828
54788
  className: "subagent-empty",
53829
54789
  children: "No sub-agent conversation data available."
53830
54790
  }, undefined, false, undefined, this);
53831
- return /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(MessageList, {
53832
- turns: session.turns,
54791
+ return /* @__PURE__ */ jsx_dev_runtime17.jsxDEV(MessageList, {
54792
+ turns: data.session.turns,
53833
54793
  sessionId,
53834
54794
  project,
53835
54795
  isSubAgent: true
53836
54796
  }, undefined, false, undefined, this);
53837
54797
  }
53838
54798
 
53839
- // src/frontend/components/project/HiddenProjectList.tsx
53840
- var import_react10 = __toESM(require_react(), 1);
53841
-
53842
54799
  // src/frontend/utils/project.ts
53843
54800
  function projectDisplayName(project) {
53844
54801
  const parts = project.name.split(/[/\\]/).filter(Boolean);
@@ -53846,56 +54803,67 @@ function projectDisplayName(project) {
53846
54803
  }
53847
54804
 
53848
54805
  // src/frontend/components/project/HiddenProjectList.tsx
53849
- var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
54806
+ var jsx_dev_runtime18 = __toESM(require_jsx_dev_runtime(), 1);
53850
54807
  function HiddenProjectList({ hiddenIds, onUnhide, onBack }) {
53851
- const [projects, setProjects] = import_react10.useState([]);
53852
- const [loading, setLoading] = import_react10.useState(true);
53853
- import_react10.useEffect(() => {
53854
- fetch("/api/projects").then((r2) => r2.json()).then((data) => {
53855
- setProjects(data.projects);
53856
- setLoading(false);
53857
- }).catch(() => setLoading(false));
53858
- }, []);
54808
+ const { data, loading, error, retry } = useFetch("/api/projects", []);
54809
+ const projects = data?.projects ?? [];
53859
54810
  const hidden = projects.filter((p) => hiddenIds.has(p.encodedPath));
53860
54811
  if (loading)
53861
- return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54812
+ return /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53862
54813
  className: "loading",
53863
54814
  children: "Loading..."
53864
54815
  }, undefined, false, undefined, this);
53865
- return /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54816
+ if (error) {
54817
+ return /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
54818
+ className: "fetch-error",
54819
+ children: [
54820
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("span", {
54821
+ className: "fetch-error-message",
54822
+ children: error
54823
+ }, undefined, false, undefined, this),
54824
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("button", {
54825
+ type: "button",
54826
+ className: "btn btn-sm",
54827
+ onClick: retry,
54828
+ children: "Retry"
54829
+ }, undefined, false, undefined, this)
54830
+ ]
54831
+ }, undefined, true, undefined, this);
54832
+ }
54833
+ return /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53866
54834
  className: "hidden-projects-page",
53867
54835
  children: [
53868
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54836
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53869
54837
  className: "back-btn",
53870
54838
  onClick: onBack,
53871
54839
  children: "← Back to projects"
53872
54840
  }, undefined, false, undefined, this),
53873
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("h2", {
54841
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("h2", {
53874
54842
  style: { margin: "16px 0 12px", fontSize: "1.1rem", color: "var(--text-primary)" },
53875
54843
  children: "Hidden Projects"
53876
54844
  }, undefined, false, undefined, this),
53877
- hidden.length === 0 ? /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54845
+ hidden.length === 0 ? /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53878
54846
  className: "empty-state",
53879
54847
  children: [
53880
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54848
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53881
54849
  className: "empty-state-title",
53882
54850
  children: "No hidden projects"
53883
54851
  }, undefined, false, undefined, this),
53884
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("p", {
54852
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("p", {
53885
54853
  children: "Projects you hide will appear here"
53886
54854
  }, undefined, false, undefined, this)
53887
54855
  ]
53888
- }, undefined, true, undefined, this) : hidden.map((project) => /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54856
+ }, undefined, true, undefined, this) : hidden.map((project) => /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53889
54857
  className: "list-item list-item-with-action",
53890
54858
  children: [
53891
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54859
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53892
54860
  className: "list-item-content",
53893
54861
  children: [
53894
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54862
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53895
54863
  className: "list-item-title",
53896
54864
  children: projectDisplayName(project)
53897
54865
  }, undefined, false, undefined, this),
53898
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("div", {
54866
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
53899
54867
  className: "list-item-meta",
53900
54868
  children: [
53901
54869
  project.sessionCount,
@@ -53905,7 +54873,7 @@ function HiddenProjectList({ hiddenIds, onUnhide, onBack }) {
53905
54873
  }, undefined, true, undefined, this)
53906
54874
  ]
53907
54875
  }, undefined, true, undefined, this),
53908
- /* @__PURE__ */ jsx_dev_runtime14.jsxDEV("button", {
54876
+ /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("button", {
53909
54877
  type: "button",
53910
54878
  className: "btn btn-sm",
53911
54879
  onClick: () => onUnhide(project.encodedPath),
@@ -53918,8 +54886,8 @@ function HiddenProjectList({ hiddenIds, onUnhide, onBack }) {
53918
54886
  }
53919
54887
 
53920
54888
  // src/frontend/components/project/ProjectList.tsx
53921
- var import_react11 = __toESM(require_react(), 1);
53922
- var jsx_dev_runtime15 = __toESM(require_jsx_dev_runtime(), 1);
54889
+ var import_react10 = __toESM(require_react(), 1);
54890
+ var jsx_dev_runtime19 = __toESM(require_jsx_dev_runtime(), 1);
53923
54891
  function ProjectList({
53924
54892
  onSelect,
53925
54893
  selected,
@@ -53927,30 +54895,41 @@ function ProjectList({
53927
54895
  onHide,
53928
54896
  onShowHidden
53929
54897
  }) {
53930
- const [projects, setProjects] = import_react11.useState([]);
53931
- const [filter, setFilter] = import_react11.useState("");
53932
- const [loading, setLoading] = import_react11.useState(true);
53933
- import_react11.useEffect(() => {
53934
- fetch("/api/projects").then((r2) => r2.json()).then((data) => {
53935
- setProjects(data.projects);
53936
- setLoading(false);
53937
- }).catch(() => setLoading(false));
53938
- }, []);
54898
+ const { data, loading, error, retry } = useFetch("/api/projects", []);
54899
+ const [filter, setFilter] = import_react10.useState("");
54900
+ const projects = data?.projects ?? [];
53939
54901
  const filtered = projects.filter((p) => !hiddenIds.has(p.encodedPath) && (p.name.toLowerCase().includes(filter.toLowerCase()) || p.encodedPath.toLowerCase().includes(filter.toLowerCase())));
53940
54902
  if (loading)
53941
- return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54903
+ return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53942
54904
  className: "loading",
53943
54905
  children: "Loading projects..."
53944
54906
  }, undefined, false, undefined, this);
53945
- return /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54907
+ if (error) {
54908
+ return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
54909
+ className: "fetch-error",
54910
+ children: [
54911
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("span", {
54912
+ className: "fetch-error-message",
54913
+ children: error
54914
+ }, undefined, false, undefined, this),
54915
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("button", {
54916
+ type: "button",
54917
+ className: "btn btn-sm",
54918
+ onClick: retry,
54919
+ children: "Retry"
54920
+ }, undefined, false, undefined, this)
54921
+ ]
54922
+ }, undefined, true, undefined, this);
54923
+ }
54924
+ return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53946
54925
  children: [
53947
- /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("input", {
54926
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("input", {
53948
54927
  className: "filter-input",
53949
54928
  placeholder: "Filter projects...",
53950
54929
  value: filter,
53951
54930
  onChange: (e) => setFilter(e.target.value)
53952
54931
  }, undefined, false, undefined, this),
53953
- /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54932
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53954
54933
  className: "list-section-title",
53955
54934
  children: [
53956
54935
  "Projects (",
@@ -53958,18 +54937,18 @@ function ProjectList({
53958
54937
  ")"
53959
54938
  ]
53960
54939
  }, undefined, true, undefined, this),
53961
- filtered.map((project) => /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54940
+ filtered.map((project) => /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53962
54941
  className: `list-item list-item-with-action ${selected === project.encodedPath ? "active" : ""}`,
53963
54942
  onClick: () => onSelect(project),
53964
54943
  children: [
53965
- /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54944
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53966
54945
  className: "list-item-content",
53967
54946
  children: [
53968
- /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54947
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53969
54948
  className: "list-item-title",
53970
54949
  children: projectDisplayName(project)
53971
54950
  }, undefined, false, undefined, this),
53972
- /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54951
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53973
54952
  className: "list-item-meta",
53974
54953
  children: [
53975
54954
  project.sessionCount,
@@ -53981,7 +54960,7 @@ function ProjectList({
53981
54960
  }, undefined, true, undefined, this)
53982
54961
  ]
53983
54962
  }, undefined, true, undefined, this),
53984
- /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("button", {
54963
+ /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("button", {
53985
54964
  type: "button",
53986
54965
  className: "btn-hide",
53987
54966
  title: "Hide project",
@@ -53993,11 +54972,11 @@ function ProjectList({
53993
54972
  }, undefined, false, undefined, this)
53994
54973
  ]
53995
54974
  }, project.encodedPath, true, undefined, this)),
53996
- filtered.length === 0 && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54975
+ filtered.length === 0 && /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
53997
54976
  className: "empty-list-message",
53998
54977
  children: "No projects found"
53999
54978
  }, undefined, false, undefined, this),
54000
- hiddenIds.size > 0 && /* @__PURE__ */ jsx_dev_runtime15.jsxDEV("div", {
54979
+ hiddenIds.size > 0 && /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
54001
54980
  className: "hidden-projects-link",
54002
54981
  onClick: onShowHidden,
54003
54982
  children: [
@@ -54011,58 +54990,65 @@ function ProjectList({
54011
54990
  }
54012
54991
 
54013
54992
  // src/frontend/components/project/SessionList.tsx
54014
- var import_react12 = __toESM(require_react(), 1);
54015
- var jsx_dev_runtime16 = __toESM(require_jsx_dev_runtime(), 1);
54993
+ var jsx_dev_runtime20 = __toESM(require_jsx_dev_runtime(), 1);
54016
54994
  function SessionList({ project, onSelect, onBack, selectedId }) {
54017
- const [sessions, setSessions] = import_react12.useState([]);
54018
- const [loading, setLoading] = import_react12.useState(true);
54019
- import_react12.useEffect(() => {
54020
- setLoading(true);
54021
- fetch(`/api/projects/${encodeURIComponent(project.encodedPath)}/sessions`).then((r2) => r2.json()).then((data) => {
54022
- setSessions(data.sessions);
54023
- setLoading(false);
54024
- }).catch(() => setLoading(false));
54025
- }, [project.encodedPath]);
54995
+ const { data, loading, error, retry } = useFetch(`/api/projects/${encodeURIComponent(project.encodedPath)}/sessions`, [project.encodedPath]);
54996
+ const sessions = data?.sessions ?? [];
54026
54997
  const parts = project.name.split("/").filter(Boolean);
54027
54998
  const displayName = parts.slice(-2).join("/");
54028
- return /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
54999
+ return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54029
55000
  children: [
54030
- /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55001
+ /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54031
55002
  className: "back-btn",
54032
55003
  onClick: onBack,
54033
55004
  children: "← Projects"
54034
55005
  }, undefined, false, undefined, this),
54035
- /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55006
+ /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54036
55007
  className: "list-section-title",
54037
55008
  children: displayName
54038
55009
  }, undefined, false, undefined, this),
54039
- loading && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55010
+ loading && /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54040
55011
  className: "loading",
54041
55012
  children: "Loading sessions..."
54042
55013
  }, undefined, false, undefined, this),
54043
- !loading && sessions.map((session) => /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55014
+ error && /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
55015
+ className: "fetch-error",
55016
+ children: [
55017
+ /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("span", {
55018
+ className: "fetch-error-message",
55019
+ children: error
55020
+ }, undefined, false, undefined, this),
55021
+ /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("button", {
55022
+ type: "button",
55023
+ className: "btn btn-sm",
55024
+ onClick: retry,
55025
+ children: "Retry"
55026
+ }, undefined, false, undefined, this)
55027
+ ]
55028
+ }, undefined, true, undefined, this),
55029
+ !loading && !error && sessions.map((session) => /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54044
55030
  className: `list-item ${selectedId === session.sessionId ? "active" : ""}${session.sessionType ? ` ${session.sessionType}` : ""}`,
54045
55031
  onClick: () => onSelect(session),
54046
55032
  children: [
54047
- /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55033
+ /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54048
55034
  className: "list-item-title",
54049
55035
  children: session.firstMessage || session.slug
54050
55036
  }, undefined, false, undefined, this),
54051
- /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55037
+ /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54052
55038
  className: "list-item-meta",
54053
55039
  children: [
54054
- session.sessionType && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("span", {
55040
+ session.sessionType && /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("span", {
54055
55041
  className: `session-type-badge ${session.sessionType}`,
54056
55042
  children: session.sessionType === "plan" ? "Plan" : "Impl"
54057
55043
  }, undefined, false, undefined, this),
54058
55044
  " ",
54059
- session.model && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("span", {
55045
+ session.model && /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("span", {
54060
55046
  children: [
54061
55047
  shortModel(session.model),
54062
55048
  " · "
54063
55049
  ]
54064
55050
  }, undefined, true, undefined, this),
54065
- session.gitBranch && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("span", {
55051
+ session.gitBranch && /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("span", {
54066
55052
  children: [
54067
55053
  session.gitBranch,
54068
55054
  " · "
@@ -54073,7 +55059,7 @@ function SessionList({ project, onSelect, onBack, selectedId }) {
54073
55059
  }, undefined, true, undefined, this)
54074
55060
  ]
54075
55061
  }, session.sessionId, true, undefined, this)),
54076
- !loading && sessions.length === 0 && /* @__PURE__ */ jsx_dev_runtime16.jsxDEV("div", {
55062
+ !loading && !error && sessions.length === 0 && /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54077
55063
  className: "empty-list-message",
54078
55064
  children: "No sessions found"
54079
55065
  }, undefined, false, undefined, this)
@@ -54082,23 +55068,23 @@ function SessionList({ project, onSelect, onBack, selectedId }) {
54082
55068
  }
54083
55069
 
54084
55070
  // src/frontend/components/search/SearchModal.tsx
54085
- var import_react13 = __toESM(require_react(), 1);
54086
- var jsx_dev_runtime17 = __toESM(require_jsx_dev_runtime(), 1);
55071
+ var import_react11 = __toESM(require_react(), 1);
55072
+ var jsx_dev_runtime21 = __toESM(require_jsx_dev_runtime(), 1);
54087
55073
  var MAX_RESULTS = 20;
54088
55074
  function matchesQuery(result, query) {
54089
55075
  const q2 = query.toLowerCase();
54090
55076
  return result.firstMessage.toLowerCase().includes(q2) || result.projectName.toLowerCase().includes(q2) || result.gitBranch.toLowerCase().includes(q2);
54091
55077
  }
54092
55078
  function SearchModal({ sessions, onSelect, onClose }) {
54093
- const [query, setQuery] = import_react13.useState("");
54094
- const [highlightedIndex, setHighlightedIndex] = import_react13.useState(0);
54095
- const inputRef = import_react13.useRef(null);
54096
- const resultsRef = import_react13.useRef(null);
55079
+ const [query, setQuery] = import_react11.useState("");
55080
+ const [highlightedIndex, setHighlightedIndex] = import_react11.useState(0);
55081
+ const inputRef = import_react11.useRef(null);
55082
+ const resultsRef = import_react11.useRef(null);
54097
55083
  const filtered = query ? sessions.filter((s2) => matchesQuery(s2, query)).slice(0, MAX_RESULTS) : sessions.slice(0, MAX_RESULTS);
54098
- import_react13.useEffect(() => {
55084
+ import_react11.useEffect(() => {
54099
55085
  inputRef.current?.focus();
54100
55086
  }, []);
54101
- import_react13.useEffect(() => {
55087
+ import_react11.useEffect(() => {
54102
55088
  const container = resultsRef.current;
54103
55089
  if (!container)
54104
55090
  return;
@@ -54108,10 +55094,10 @@ function SearchModal({ sessions, onSelect, onClose }) {
54108
55094
  item.scrollIntoView({ block: "nearest" });
54109
55095
  }
54110
55096
  }, [highlightedIndex]);
54111
- const handleSelect = import_react13.useCallback((result) => {
55097
+ const handleSelect = import_react11.useCallback((result) => {
54112
55098
  onSelect(result.encodedPath, result.sessionId);
54113
55099
  }, [onSelect]);
54114
- const handleKeyDown = import_react13.useCallback((e) => {
55100
+ const handleKeyDown = import_react11.useCallback((e) => {
54115
55101
  switch (e.key) {
54116
55102
  case "ArrowDown":
54117
55103
  e.preventDefault();
@@ -54133,16 +55119,16 @@ function SearchModal({ sessions, onSelect, onClose }) {
54133
55119
  break;
54134
55120
  }
54135
55121
  }, [filtered, highlightedIndex, handleSelect, onClose]);
54136
- return /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55122
+ return /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54137
55123
  className: "search-overlay",
54138
55124
  onMouseDown: onClose,
54139
- children: /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55125
+ children: /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54140
55126
  className: "search-modal",
54141
55127
  onMouseDown: (e) => e.stopPropagation(),
54142
55128
  children: [
54143
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55129
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54144
55130
  className: "search-input-wrapper",
54145
- children: /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("input", {
55131
+ children: /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("input", {
54146
55132
  ref: inputRef,
54147
55133
  className: "search-input",
54148
55134
  type: "text",
@@ -54155,31 +55141,31 @@ function SearchModal({ sessions, onSelect, onClose }) {
54155
55141
  onKeyDown: handleKeyDown
54156
55142
  }, undefined, false, undefined, this)
54157
55143
  }, undefined, false, undefined, this),
54158
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55144
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54159
55145
  className: "search-results",
54160
55146
  ref: resultsRef,
54161
- children: filtered.length === 0 ? /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55147
+ children: filtered.length === 0 ? /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54162
55148
  className: "search-empty",
54163
55149
  children: "No results found"
54164
- }, undefined, false, undefined, this) : filtered.map((result, index2) => /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55150
+ }, undefined, false, undefined, this) : filtered.map((result, index2) => /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54165
55151
  className: `search-result-item ${index2 === highlightedIndex ? "highlighted" : ""}`,
54166
55152
  "data-search-item": true,
54167
55153
  onClick: () => handleSelect(result),
54168
55154
  onMouseEnter: () => setHighlightedIndex(index2),
54169
55155
  children: [
54170
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55156
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54171
55157
  className: "search-result-title",
54172
55158
  children: [
54173
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("span", {
55159
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("span", {
54174
55160
  children: result.firstMessage
54175
55161
  }, undefined, false, undefined, this),
54176
- result.sessionType && /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("span", {
55162
+ result.sessionType && /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("span", {
54177
55163
  className: `session-type-badge ${result.sessionType}`,
54178
55164
  children: result.sessionType
54179
55165
  }, undefined, false, undefined, this)
54180
55166
  ]
54181
55167
  }, undefined, true, undefined, this),
54182
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55168
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54183
55169
  className: "search-result-meta",
54184
55170
  children: [
54185
55171
  result.projectName,
@@ -54194,28 +55180,28 @@ function SearchModal({ sessions, onSelect, onClose }) {
54194
55180
  ]
54195
55181
  }, `${result.encodedPath}-${result.sessionId}`, true, undefined, this))
54196
55182
  }, undefined, false, undefined, this),
54197
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("div", {
55183
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
54198
55184
  className: "search-footer",
54199
55185
  children: [
54200
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("span", {
55186
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("span", {
54201
55187
  children: [
54202
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("kbd", {
55188
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("kbd", {
54203
55189
  children: "↑↓"
54204
55190
  }, undefined, false, undefined, this),
54205
55191
  " navigate"
54206
55192
  ]
54207
55193
  }, undefined, true, undefined, this),
54208
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("span", {
55194
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("span", {
54209
55195
  children: [
54210
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("kbd", {
55196
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("kbd", {
54211
55197
  children: "↵"
54212
55198
  }, undefined, false, undefined, this),
54213
55199
  " open"
54214
55200
  ]
54215
55201
  }, undefined, true, undefined, this),
54216
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("span", {
55202
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("span", {
54217
55203
  children: [
54218
- /* @__PURE__ */ jsx_dev_runtime17.jsxDEV("kbd", {
55204
+ /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("kbd", {
54219
55205
  children: "esc"
54220
55206
  }, undefined, false, undefined, this),
54221
55207
  " close"
@@ -54228,16 +55214,13 @@ function SearchModal({ sessions, onSelect, onClose }) {
54228
55214
  }, undefined, false, undefined, this);
54229
55215
  }
54230
55216
 
54231
- // src/frontend/components/session/SessionPresentation.tsx
54232
- var import_react17 = __toESM(require_react(), 1);
54233
-
54234
55217
  // src/frontend/components/session/PresentationShell.tsx
54235
- var import_react16 = __toESM(require_react(), 1);
55218
+ var import_react14 = __toESM(require_react(), 1);
54236
55219
 
54237
55220
  // src/frontend/hooks/useKeyboard.ts
54238
- var import_react14 = __toESM(require_react(), 1);
55221
+ var import_react12 = __toESM(require_react(), 1);
54239
55222
  function useKeyboard(handlers2, active) {
54240
- import_react14.useEffect(() => {
55223
+ import_react12.useEffect(() => {
54241
55224
  if (!active)
54242
55225
  return;
54243
55226
  function handleKeyDown(e) {
@@ -54280,7 +55263,7 @@ function useKeyboard(handlers2, active) {
54280
55263
  }
54281
55264
 
54282
55265
  // src/frontend/hooks/usePresentationMode.ts
54283
- var import_react15 = __toESM(require_react(), 1);
55266
+ var import_react13 = __toESM(require_react(), 1);
54284
55267
  function countSubSteps(turn) {
54285
55268
  if (turn.kind !== "assistant")
54286
55269
  return 1;
@@ -54288,10 +55271,10 @@ function countSubSteps(turn) {
54288
55271
  return Math.max(groupContentBlocks(a.contentBlocks).length, 1);
54289
55272
  }
54290
55273
  function usePresentationMode(turns) {
54291
- const [active, setActive] = import_react15.useState(false);
54292
- const [fullscreen, setFullscreen] = import_react15.useState(false);
54293
- const [currentStep, setCurrentStep] = import_react15.useState(0);
54294
- const steps = import_react15.useMemo(() => {
55274
+ const [active, setActive] = import_react13.useState(false);
55275
+ const [fullscreen, setFullscreen] = import_react13.useState(false);
55276
+ const [currentStep, setCurrentStep] = import_react13.useState(0);
55277
+ const steps = import_react13.useMemo(() => {
54295
55278
  const result = [];
54296
55279
  for (let i = 0;i < turns.length; i++) {
54297
55280
  const sub = countSubSteps(turns[i]);
@@ -54302,7 +55285,7 @@ function usePresentationMode(turns) {
54302
55285
  return result;
54303
55286
  }, [turns]);
54304
55287
  const totalSteps = steps.length;
54305
- const turnBoundaries = import_react15.useMemo(() => {
55288
+ const turnBoundaries = import_react13.useMemo(() => {
54306
55289
  const boundaries = [];
54307
55290
  for (let i = 0;i < steps.length; i++) {
54308
55291
  const next2 = steps[i + 1];
@@ -54312,7 +55295,7 @@ function usePresentationMode(turns) {
54312
55295
  }
54313
55296
  return boundaries;
54314
55297
  }, [steps]);
54315
- const { visibleTurns, visibleSubSteps } = import_react15.useMemo(() => {
55298
+ const { visibleTurns, visibleSubSteps } = import_react13.useMemo(() => {
54316
55299
  if (!active || steps.length === 0) {
54317
55300
  return { visibleTurns: turns, visibleSubSteps: new Map };
54318
55301
  }
@@ -54326,22 +55309,22 @@ function usePresentationMode(turns) {
54326
55309
  subSteps.set(maxTurnIndex, step.subStep + 1);
54327
55310
  return { visibleTurns: visible, visibleSubSteps: subSteps };
54328
55311
  }, [active, currentStep, steps, turns]);
54329
- const enter = import_react15.useCallback(() => {
55312
+ const enter = import_react13.useCallback(() => {
54330
55313
  setActive(true);
54331
55314
  setCurrentStep(0);
54332
55315
  }, []);
54333
- const exit3 = import_react15.useCallback(() => {
55316
+ const exit3 = import_react13.useCallback(() => {
54334
55317
  setActive(false);
54335
55318
  setFullscreen(false);
54336
55319
  setCurrentStep(0);
54337
55320
  }, []);
54338
- const next = import_react15.useCallback(() => {
55321
+ const next = import_react13.useCallback(() => {
54339
55322
  setCurrentStep((s2) => Math.min(s2 + 1, steps.length - 1));
54340
55323
  }, [steps.length]);
54341
- const prev = import_react15.useCallback(() => {
55324
+ const prev = import_react13.useCallback(() => {
54342
55325
  setCurrentStep((s2) => Math.max(s2 - 1, 0));
54343
55326
  }, []);
54344
- const nextTurn = import_react15.useCallback(() => {
55327
+ const nextTurn = import_react13.useCallback(() => {
54345
55328
  setCurrentStep((s2) => {
54346
55329
  for (const b of turnBoundaries) {
54347
55330
  if (b > s2)
@@ -54350,7 +55333,7 @@ function usePresentationMode(turns) {
54350
55333
  return s2;
54351
55334
  });
54352
55335
  }, [turnBoundaries]);
54353
- const prevTurn = import_react15.useCallback(() => {
55336
+ const prevTurn = import_react13.useCallback(() => {
54354
55337
  setCurrentStep((s2) => {
54355
55338
  for (let i = turnBoundaries.length - 1;i >= 0; i--) {
54356
55339
  if (turnBoundaries[i] < s2)
@@ -54359,7 +55342,7 @@ function usePresentationMode(turns) {
54359
55342
  return s2;
54360
55343
  });
54361
55344
  }, [turnBoundaries]);
54362
- const toggleFullscreen = import_react15.useCallback(() => {
55345
+ const toggleFullscreen = import_react13.useCallback(() => {
54363
55346
  setFullscreen((f) => !f);
54364
55347
  }, []);
54365
55348
  return {
@@ -54380,7 +55363,7 @@ function usePresentationMode(turns) {
54380
55363
  }
54381
55364
 
54382
55365
  // src/frontend/components/session/PresentationShell.tsx
54383
- var jsx_dev_runtime18 = __toESM(require_jsx_dev_runtime(), 1);
55366
+ var jsx_dev_runtime22 = __toESM(require_jsx_dev_runtime(), 1);
54384
55367
  function PresentationShell({
54385
55368
  turns,
54386
55369
  onExit,
@@ -54388,9 +55371,9 @@ function PresentationShell({
54388
55371
  project,
54389
55372
  isSubAgent
54390
55373
  }) {
54391
- const scrollRef = import_react16.useRef(null);
55374
+ const scrollRef = import_react14.useRef(null);
54392
55375
  const presentation = usePresentationMode(turns);
54393
- import_react16.useEffect(() => {
55376
+ import_react14.useEffect(() => {
54394
55377
  if (turns.length > 0 && !presentation.active) {
54395
55378
  presentation.enter();
54396
55379
  }
@@ -54404,28 +55387,28 @@ function PresentationShell({
54404
55387
  onFullscreen: presentation.toggleFullscreen
54405
55388
  }, presentation.active);
54406
55389
  const currentStep = presentation.currentStep;
54407
- import_react16.useEffect(() => {
55390
+ import_react14.useEffect(() => {
54408
55391
  if (currentStep >= 0 && scrollRef.current) {
54409
55392
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
54410
55393
  }
54411
55394
  }, [currentStep]);
54412
55395
  const progress = presentation.totalSteps > 0 ? (presentation.currentStep + 1) / presentation.totalSteps * 100 : 0;
54413
- return /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
55396
+ return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
54414
55397
  className: `presentation-mode ${presentation.fullscreen ? "fullscreen" : ""}`,
54415
55398
  ref: scrollRef,
54416
55399
  style: { overflowY: "auto", height: "calc(100vh - 92px)" },
54417
55400
  children: [
54418
- /* @__PURE__ */ jsx_dev_runtime18.jsxDEV(MessageList, {
55401
+ /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(MessageList, {
54419
55402
  turns: presentation.visibleTurns,
54420
55403
  visibleSubSteps: presentation.visibleSubSteps,
54421
55404
  sessionId,
54422
55405
  project,
54423
55406
  isSubAgent
54424
55407
  }, undefined, false, undefined, this),
54425
- /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
55408
+ /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
54426
55409
  className: "presentation-progress",
54427
55410
  children: [
54428
- /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("span", {
55411
+ /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("span", {
54429
55412
  children: [
54430
55413
  "Step ",
54431
55414
  presentation.currentStep + 1,
@@ -54433,14 +55416,14 @@ function PresentationShell({
54433
55416
  presentation.totalSteps
54434
55417
  ]
54435
55418
  }, undefined, true, undefined, this),
54436
- /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
55419
+ /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
54437
55420
  className: "presentation-progress-bar",
54438
- children: /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("div", {
55421
+ children: /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
54439
55422
  className: "presentation-progress-fill",
54440
55423
  style: { width: `${progress}%` }
54441
55424
  }, undefined, false, undefined, this)
54442
55425
  }, undefined, false, undefined, this),
54443
- /* @__PURE__ */ jsx_dev_runtime18.jsxDEV("span", {
55426
+ /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("span", {
54444
55427
  style: { fontSize: "0.75rem" },
54445
55428
  children: "← → step · ↑ ↓ message · Esc exit · F fullscreen"
54446
55429
  }, undefined, false, undefined, this)
@@ -54451,26 +55434,38 @@ function PresentationShell({
54451
55434
  }
54452
55435
 
54453
55436
  // src/frontend/components/session/SessionPresentation.tsx
54454
- var jsx_dev_runtime19 = __toESM(require_jsx_dev_runtime(), 1);
55437
+ var jsx_dev_runtime23 = __toESM(require_jsx_dev_runtime(), 1);
54455
55438
  function SessionPresentation({ sessionId, project, onExit }) {
54456
- const [session, setSession] = import_react17.useState(null);
54457
- const [loading, setLoading] = import_react17.useState(true);
54458
- import_react17.useEffect(() => {
54459
- setLoading(true);
54460
- fetch(`/api/sessions/${sessionId}?project=${encodeURIComponent(project)}`).then((r2) => r2.json()).then((data) => {
54461
- setSession(data.session);
54462
- setLoading(false);
54463
- }).catch(() => setLoading(false));
54464
- }, [sessionId, project]);
55439
+ const { data, loading, error, retry } = useFetch(`/api/sessions/${sessionId}?project=${encodeURIComponent(project)}`, [sessionId, project]);
54465
55440
  if (loading)
54466
- return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV("div", {
55441
+ return /* @__PURE__ */ jsx_dev_runtime23.jsxDEV("div", {
54467
55442
  className: "loading",
54468
55443
  children: "Loading session..."
54469
55444
  }, undefined, false, undefined, this);
54470
- if (!session)
55445
+ if (error) {
55446
+ return /* @__PURE__ */ jsx_dev_runtime23.jsxDEV("div", {
55447
+ className: "fetch-error",
55448
+ children: [
55449
+ /* @__PURE__ */ jsx_dev_runtime23.jsxDEV("span", {
55450
+ className: "fetch-error-message",
55451
+ children: [
55452
+ "Error: ",
55453
+ error
55454
+ ]
55455
+ }, undefined, true, undefined, this),
55456
+ /* @__PURE__ */ jsx_dev_runtime23.jsxDEV("button", {
55457
+ type: "button",
55458
+ className: "btn btn-sm",
55459
+ onClick: retry,
55460
+ children: "Retry"
55461
+ }, undefined, false, undefined, this)
55462
+ ]
55463
+ }, undefined, true, undefined, this);
55464
+ }
55465
+ if (!data?.session)
54471
55466
  return null;
54472
- return /* @__PURE__ */ jsx_dev_runtime19.jsxDEV(PresentationShell, {
54473
- turns: session.turns,
55467
+ return /* @__PURE__ */ jsx_dev_runtime23.jsxDEV(PresentationShell, {
55468
+ turns: data.session.turns,
54474
55469
  onExit,
54475
55470
  sessionId,
54476
55471
  project
@@ -54478,44 +55473,38 @@ function SessionPresentation({ sessionId, project, onExit }) {
54478
55473
  }
54479
55474
 
54480
55475
  // src/frontend/components/session/SessionView.tsx
54481
- var import_react18 = __toESM(require_react(), 1);
54482
- var jsx_dev_runtime20 = __toESM(require_jsx_dev_runtime(), 1);
55476
+ var jsx_dev_runtime24 = __toESM(require_jsx_dev_runtime(), 1);
54483
55477
  function SessionView({ sessionId, project }) {
54484
- const [session, setSession] = import_react18.useState(null);
54485
- const [loading, setLoading] = import_react18.useState(true);
54486
- const [error, setError] = import_react18.useState(null);
54487
- import_react18.useEffect(() => {
54488
- setLoading(true);
54489
- setError(null);
54490
- fetch(`/api/sessions/${sessionId}?project=${encodeURIComponent(project)}`).then((r2) => {
54491
- if (!r2.ok)
54492
- throw new Error(`HTTP ${r2.status}`);
54493
- return r2.json();
54494
- }).then((data) => {
54495
- setSession(data.session);
54496
- setLoading(false);
54497
- }).catch((e) => {
54498
- setError(e.message);
54499
- setLoading(false);
54500
- });
54501
- }, [sessionId, project]);
55478
+ const { data, loading, error, retry } = useFetch(`/api/sessions/${sessionId}?project=${encodeURIComponent(project)}`, [sessionId, project]);
54502
55479
  if (loading)
54503
- return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
55480
+ return /* @__PURE__ */ jsx_dev_runtime24.jsxDEV("div", {
54504
55481
  className: "loading",
54505
55482
  children: "Loading session..."
54506
55483
  }, undefined, false, undefined, this);
54507
- if (error)
54508
- return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV("div", {
54509
- className: "loading",
54510
- style: { color: "var(--error)" },
55484
+ if (error) {
55485
+ return /* @__PURE__ */ jsx_dev_runtime24.jsxDEV("div", {
55486
+ className: "fetch-error",
54511
55487
  children: [
54512
- "Error: ",
54513
- error
55488
+ /* @__PURE__ */ jsx_dev_runtime24.jsxDEV("span", {
55489
+ className: "fetch-error-message",
55490
+ children: [
55491
+ "Error: ",
55492
+ error
55493
+ ]
55494
+ }, undefined, true, undefined, this),
55495
+ /* @__PURE__ */ jsx_dev_runtime24.jsxDEV("button", {
55496
+ type: "button",
55497
+ className: "btn btn-sm",
55498
+ onClick: retry,
55499
+ children: "Retry"
55500
+ }, undefined, false, undefined, this)
54514
55501
  ]
54515
55502
  }, undefined, true, undefined, this);
54516
- if (!session)
55503
+ }
55504
+ if (!data?.session)
54517
55505
  return null;
54518
- return /* @__PURE__ */ jsx_dev_runtime20.jsxDEV(MessageList, {
55506
+ const session = data.session;
55507
+ return /* @__PURE__ */ jsx_dev_runtime24.jsxDEV(MessageList, {
54519
55508
  turns: session.turns,
54520
55509
  sessionId,
54521
55510
  project,
@@ -54525,32 +55514,43 @@ function SessionView({ sessionId, project }) {
54525
55514
  }
54526
55515
 
54527
55516
  // src/frontend/components/session/SubAgentPresentation.tsx
54528
- var import_react19 = __toESM(require_react(), 1);
54529
- var jsx_dev_runtime21 = __toESM(require_jsx_dev_runtime(), 1);
55517
+ var jsx_dev_runtime25 = __toESM(require_jsx_dev_runtime(), 1);
54530
55518
  function SubAgentPresentation({
54531
55519
  sessionId,
54532
55520
  project,
54533
55521
  agentId,
54534
55522
  onExit
54535
55523
  }) {
54536
- const [session, setSession] = import_react19.useState(null);
54537
- const [loading, setLoading] = import_react19.useState(true);
54538
- import_react19.useEffect(() => {
54539
- setLoading(true);
54540
- fetch(`/api/sessions/${sessionId}/subagents/${agentId}?project=${encodeURIComponent(project)}`).then((r2) => r2.json()).then((data) => {
54541
- setSession(data.session);
54542
- setLoading(false);
54543
- }).catch(() => setLoading(false));
54544
- }, [sessionId, project, agentId]);
55524
+ const { data, loading, error, retry } = useFetch(`/api/sessions/${sessionId}/subagents/${agentId}?project=${encodeURIComponent(project)}`, [sessionId, project, agentId]);
54545
55525
  if (loading)
54546
- return /* @__PURE__ */ jsx_dev_runtime21.jsxDEV("div", {
55526
+ return /* @__PURE__ */ jsx_dev_runtime25.jsxDEV("div", {
54547
55527
  className: "loading",
54548
55528
  children: "Loading sub-agent conversation..."
54549
55529
  }, undefined, false, undefined, this);
54550
- if (!session || session.turns.length === 0)
55530
+ if (error) {
55531
+ return /* @__PURE__ */ jsx_dev_runtime25.jsxDEV("div", {
55532
+ className: "fetch-error",
55533
+ children: [
55534
+ /* @__PURE__ */ jsx_dev_runtime25.jsxDEV("span", {
55535
+ className: "fetch-error-message",
55536
+ children: [
55537
+ "Error: ",
55538
+ error
55539
+ ]
55540
+ }, undefined, true, undefined, this),
55541
+ /* @__PURE__ */ jsx_dev_runtime25.jsxDEV("button", {
55542
+ type: "button",
55543
+ className: "btn btn-sm",
55544
+ onClick: retry,
55545
+ children: "Retry"
55546
+ }, undefined, false, undefined, this)
55547
+ ]
55548
+ }, undefined, true, undefined, this);
55549
+ }
55550
+ if (!data?.session || data.session.turns.length === 0)
54551
55551
  return null;
54552
- return /* @__PURE__ */ jsx_dev_runtime21.jsxDEV(PresentationShell, {
54553
- turns: session.turns,
55552
+ return /* @__PURE__ */ jsx_dev_runtime25.jsxDEV(PresentationShell, {
55553
+ turns: data.session.turns,
54554
55554
  onExit,
54555
55555
  sessionId,
54556
55556
  project,
@@ -54559,7 +55559,7 @@ function SubAgentPresentation({
54559
55559
  }
54560
55560
 
54561
55561
  // src/frontend/hooks/useHiddenProjects.ts
54562
- var import_react20 = __toESM(require_react(), 1);
55562
+ var import_react15 = __toESM(require_react(), 1);
54563
55563
  var STORAGE_KEY = "klovi-hidden-projects";
54564
55564
  function loadHiddenIds() {
54565
55565
  try {
@@ -54581,8 +55581,8 @@ function persistHiddenIds(ids) {
54581
55581
  localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
54582
55582
  }
54583
55583
  function useHiddenProjects() {
54584
- const [hiddenIds, setHiddenIds] = import_react20.useState(() => loadHiddenIds());
54585
- const hide = import_react20.useCallback((encodedPath) => {
55584
+ const [hiddenIds, setHiddenIds] = import_react15.useState(() => loadHiddenIds());
55585
+ const hide = import_react15.useCallback((encodedPath) => {
54586
55586
  setHiddenIds((prev) => {
54587
55587
  const next = new Set(prev);
54588
55588
  next.add(encodedPath);
@@ -54590,7 +55590,7 @@ function useHiddenProjects() {
54590
55590
  return next;
54591
55591
  });
54592
55592
  }, []);
54593
- const unhide = import_react20.useCallback((encodedPath) => {
55593
+ const unhide = import_react15.useCallback((encodedPath) => {
54594
55594
  setHiddenIds((prev) => {
54595
55595
  const next = new Set(prev);
54596
55596
  next.delete(encodedPath);
@@ -54598,70 +55598,12 @@ function useHiddenProjects() {
54598
55598
  return next;
54599
55599
  });
54600
55600
  }, []);
54601
- const isHidden = import_react20.useCallback((encodedPath) => hiddenIds.has(encodedPath), [hiddenIds]);
55601
+ const isHidden = import_react15.useCallback((encodedPath) => hiddenIds.has(encodedPath), [hiddenIds]);
54602
55602
  return { hiddenIds, hide, unhide, isHidden };
54603
55603
  }
54604
55604
 
54605
- // src/frontend/hooks/useTheme.ts
54606
- var import_react21 = __toESM(require_react(), 1);
54607
- function getSystemTheme() {
54608
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
54609
- }
54610
- function resolveTheme(setting) {
54611
- if (setting === "system")
54612
- return getSystemTheme();
54613
- return setting;
54614
- }
54615
- function useTheme() {
54616
- const [setting, setSetting] = import_react21.useState(() => {
54617
- const stored = localStorage.getItem("klovi-theme");
54618
- if (stored === "light" || stored === "dark")
54619
- return stored;
54620
- return "system";
54621
- });
54622
- const [resolved, setResolved] = import_react21.useState(() => resolveTheme(setting));
54623
- import_react21.useEffect(() => {
54624
- document.documentElement.setAttribute("data-theme", resolved);
54625
- }, [resolved]);
54626
- import_react21.useEffect(() => {
54627
- localStorage.setItem("klovi-theme", setting);
54628
- setResolved(resolveTheme(setting));
54629
- }, [setting]);
54630
- import_react21.useEffect(() => {
54631
- if (setting !== "system")
54632
- return;
54633
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
54634
- const handler = () => setResolved(getSystemTheme());
54635
- mq.addEventListener("change", handler);
54636
- return () => mq.removeEventListener("change", handler);
54637
- }, [setting]);
54638
- const cycle = import_react21.useCallback(() => {
54639
- setSetting((s2) => {
54640
- if (s2 === "system")
54641
- return "light";
54642
- if (s2 === "light")
54643
- return "dark";
54644
- return "system";
54645
- });
54646
- }, []);
54647
- return { setting, resolved, cycle };
54648
- }
54649
- function useFontSize() {
54650
- const [size, setSize] = import_react21.useState(() => {
54651
- const stored = localStorage.getItem("klovi-font-size");
54652
- return stored ? parseInt(stored, 10) : 15;
54653
- });
54654
- import_react21.useEffect(() => {
54655
- document.documentElement.style.setProperty("--font-size-base", `${size}px`);
54656
- localStorage.setItem("klovi-font-size", String(size));
54657
- }, [size]);
54658
- const increase = import_react21.useCallback(() => setSize((s2) => Math.min(s2 + 2, 28)), []);
54659
- const decrease = import_react21.useCallback(() => setSize((s2) => Math.max(s2 - 2, 10)), []);
54660
- return { size, increase, decrease };
54661
- }
54662
-
54663
55605
  // src/frontend/App.tsx
54664
- var jsx_dev_runtime22 = __toESM(require_jsx_dev_runtime(), 1);
55606
+ var jsx_dev_runtime26 = __toESM(require_jsx_dev_runtime(), 1);
54665
55607
  function viewToHash(view) {
54666
55608
  if (view.kind === "hidden")
54667
55609
  return "#/hidden";
@@ -54734,7 +55676,7 @@ function getHeaderInfo(view) {
54734
55676
  }
54735
55677
  function getSidebarContent(view, selectProject, hiddenIds, hide, goHidden, selectSession, goHome) {
54736
55678
  if (view.kind === "home" || view.kind === "hidden") {
54737
- return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(ProjectList, {
55679
+ return /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(ProjectList, {
54738
55680
  onSelect: selectProject,
54739
55681
  hiddenIds,
54740
55682
  onHide: hide,
@@ -54742,21 +55684,21 @@ function getSidebarContent(view, selectProject, hiddenIds, hide, goHidden, selec
54742
55684
  }, undefined, false, undefined, this);
54743
55685
  }
54744
55686
  if (view.kind === "project") {
54745
- return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SessionList, {
55687
+ return /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SessionList, {
54746
55688
  project: view.project,
54747
55689
  onSelect: selectSession,
54748
55690
  onBack: goHome
54749
55691
  }, undefined, false, undefined, this);
54750
55692
  }
54751
55693
  if (view.kind === "subagent") {
54752
- return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SessionList, {
55694
+ return /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SessionList, {
54753
55695
  project: view.project,
54754
55696
  onSelect: selectSession,
54755
55697
  onBack: goHome,
54756
55698
  selectedId: view.sessionId
54757
55699
  }, undefined, false, undefined, this);
54758
55700
  }
54759
- return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SessionList, {
55701
+ return /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SessionList, {
54760
55702
  project: view.project,
54761
55703
  onSelect: selectSession,
54762
55704
  onBack: goHome,
@@ -54767,17 +55709,17 @@ function App() {
54767
55709
  const { setting: themeSetting, cycle: cycleTheme } = useTheme();
54768
55710
  const { size: fontSize, increase, decrease } = useFontSize();
54769
55711
  const { hiddenIds, hide, unhide } = useHiddenProjects();
54770
- const [view, setView] = import_react22.useState({ kind: "home" });
54771
- const [ready, setReady] = import_react22.useState(false);
54772
- const [searchOpen, setSearchOpen] = import_react22.useState(false);
54773
- const [searchSessions, setSearchSessions] = import_react22.useState([]);
54774
- import_react22.useEffect(() => {
55712
+ const [view, setView] = import_react16.useState({ kind: "home" });
55713
+ const [ready, setReady] = import_react16.useState(false);
55714
+ const [searchOpen, setSearchOpen] = import_react16.useState(false);
55715
+ const [searchSessions, setSearchSessions] = import_react16.useState([]);
55716
+ import_react16.useEffect(() => {
54775
55717
  restoreFromHash().then((v2) => {
54776
55718
  setView(v2);
54777
55719
  setReady(true);
54778
55720
  });
54779
55721
  }, []);
54780
- import_react22.useEffect(() => {
55722
+ import_react16.useEffect(() => {
54781
55723
  if (!ready)
54782
55724
  return;
54783
55725
  const newHash = viewToHash(view);
@@ -54785,7 +55727,7 @@ function App() {
54785
55727
  history.pushState(null, "", newHash);
54786
55728
  }
54787
55729
  }, [view, ready]);
54788
- import_react22.useEffect(() => {
55730
+ import_react16.useEffect(() => {
54789
55731
  const handler = () => {
54790
55732
  restoreFromHash().then(setView);
54791
55733
  };
@@ -54808,19 +55750,19 @@ function App() {
54808
55750
  const goHome = () => setView({ kind: "home" });
54809
55751
  const goHidden = () => setView({ kind: "hidden" });
54810
55752
  const canPresent = view.kind === "session" || view.kind === "subagent";
54811
- const togglePresentation = import_react22.useCallback(() => {
55753
+ const togglePresentation = import_react16.useCallback(() => {
54812
55754
  if (view.kind === "session" || view.kind === "subagent") {
54813
55755
  setView({ ...view, presenting: !view.presenting });
54814
55756
  }
54815
55757
  }, [view]);
54816
- const fetchSearchSessions = import_react22.useCallback(() => {
55758
+ const fetchSearchSessions = import_react16.useCallback(() => {
54817
55759
  fetch("/api/search/sessions").then((res) => res.json()).then((data) => setSearchSessions(data.sessions)).catch(() => {});
54818
55760
  }, []);
54819
- const openSearch = import_react22.useCallback(() => {
55761
+ const openSearch = import_react16.useCallback(() => {
54820
55762
  setSearchOpen(true);
54821
55763
  fetchSearchSessions();
54822
55764
  }, [fetchSearchSessions]);
54823
- const handleSearchSelect = import_react22.useCallback(async (encodedPath, sessionId) => {
55765
+ const handleSearchSelect = import_react16.useCallback(async (encodedPath, sessionId) => {
54824
55766
  setSearchOpen(false);
54825
55767
  try {
54826
55768
  const [projectsRes, sessionsRes] = await Promise.all([
@@ -54836,7 +55778,7 @@ function App() {
54836
55778
  }
54837
55779
  } catch {}
54838
55780
  }, []);
54839
- import_react22.useEffect(() => {
55781
+ import_react16.useEffect(() => {
54840
55782
  function handleCmdK(e) {
54841
55783
  if ((e.metaKey || e.ctrlKey) && e.key === "k") {
54842
55784
  e.preventDefault();
@@ -54850,7 +55792,7 @@ function App() {
54850
55792
  window.addEventListener("keydown", handleCmdK);
54851
55793
  return () => window.removeEventListener("keydown", handleCmdK);
54852
55794
  }, [fetchSearchSessions]);
54853
- import_react22.useEffect(() => {
55795
+ import_react16.useEffect(() => {
54854
55796
  function handleKeyDown(e) {
54855
55797
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement)
54856
55798
  return;
@@ -54881,24 +55823,24 @@ function App() {
54881
55823
  const sidebarContent = getSidebarContent(view, selectProject, hiddenIds, hide, goHidden, selectSession, goHome);
54882
55824
  const isPresenting = canPresent && view.presenting;
54883
55825
  if (!ready) {
54884
- return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
55826
+ return /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("div", {
54885
55827
  className: "loading",
54886
55828
  children: "Loading..."
54887
55829
  }, undefined, false, undefined, this);
54888
55830
  }
54889
- return /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(jsx_dev_runtime22.Fragment, {
55831
+ return /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(jsx_dev_runtime26.Fragment, {
54890
55832
  children: [
54891
- searchOpen && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SearchModal, {
55833
+ searchOpen && /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SearchModal, {
54892
55834
  sessions: searchSessions,
54893
55835
  onSelect: handleSearchSelect,
54894
55836
  onClose: () => setSearchOpen(false)
54895
55837
  }, undefined, false, undefined, this),
54896
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Layout, {
55838
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(Layout, {
54897
55839
  sidebar: sidebarContent,
54898
55840
  hideSidebar: isPresenting,
54899
55841
  onSearchClick: openSearch,
54900
55842
  children: [
54901
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(Header, {
55843
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(Header, {
54902
55844
  title: headerTitle,
54903
55845
  breadcrumb,
54904
55846
  copyCommand: view.kind === "session" ? `claude --resume ${view.session.sessionId}` : undefined,
@@ -54913,69 +55855,73 @@ function App() {
54913
55855
  onTogglePresentation: togglePresentation,
54914
55856
  showPresentationToggle: canPresent
54915
55857
  }, undefined, false, undefined, this),
54916
- view.kind === "home" && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(jsx_dev_runtime22.Fragment, {
55858
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(ErrorBoundary, {
54917
55859
  children: [
54918
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
55860
+ view.kind === "home" && /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(jsx_dev_runtime26.Fragment, {
55861
+ children: [
55862
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("div", {
55863
+ className: "empty-state",
55864
+ children: [
55865
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("img", {
55866
+ src: favicon_default,
55867
+ alt: "",
55868
+ width: "64",
55869
+ height: "64",
55870
+ className: "empty-state-logo"
55871
+ }, undefined, false, undefined, this),
55872
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("div", {
55873
+ className: "empty-state-title",
55874
+ children: "Welcome to Klovi"
55875
+ }, undefined, false, undefined, this),
55876
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("p", {
55877
+ children: "Select a project from the sidebar to browse your Claude Code sessions"
55878
+ }, undefined, false, undefined, this)
55879
+ ]
55880
+ }, undefined, true, undefined, this),
55881
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(DashboardStats, {}, undefined, false, undefined, this)
55882
+ ]
55883
+ }, undefined, true, undefined, this),
55884
+ view.kind === "hidden" && /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(HiddenProjectList, {
55885
+ hiddenIds,
55886
+ onUnhide: unhide,
55887
+ onBack: goHome
55888
+ }, undefined, false, undefined, this),
55889
+ view.kind === "project" && /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("div", {
54919
55890
  className: "empty-state",
54920
55891
  children: [
54921
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("img", {
54922
- src: favicon_default,
54923
- alt: "",
54924
- width: "64",
54925
- height: "64",
54926
- className: "empty-state-logo"
54927
- }, undefined, false, undefined, this),
54928
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
55892
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("div", {
54929
55893
  className: "empty-state-title",
54930
- children: "Welcome to Klovi"
55894
+ children: "Select a session"
54931
55895
  }, undefined, false, undefined, this),
54932
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("p", {
54933
- children: "Select a project from the sidebar to browse your Claude Code sessions"
55896
+ /* @__PURE__ */ jsx_dev_runtime26.jsxDEV("p", {
55897
+ children: "Choose a conversation from the sidebar"
54934
55898
  }, undefined, false, undefined, this)
54935
55899
  ]
54936
55900
  }, undefined, true, undefined, this),
54937
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(DashboardStats, {}, undefined, false, undefined, this)
55901
+ view.kind === "session" && (view.presenting ? /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SessionPresentation, {
55902
+ sessionId: view.session.sessionId,
55903
+ project: view.project.encodedPath,
55904
+ onExit: togglePresentation
55905
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SessionView, {
55906
+ sessionId: view.session.sessionId,
55907
+ project: view.project.encodedPath
55908
+ }, undefined, false, undefined, this)),
55909
+ view.kind === "subagent" && (view.presenting ? /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SubAgentPresentation, {
55910
+ sessionId: view.sessionId,
55911
+ project: view.project.encodedPath,
55912
+ agentId: view.agentId,
55913
+ onExit: togglePresentation
55914
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime26.jsxDEV(SubAgentView, {
55915
+ sessionId: view.sessionId,
55916
+ project: view.project.encodedPath,
55917
+ agentId: view.agentId
55918
+ }, undefined, false, undefined, this))
54938
55919
  ]
54939
- }, undefined, true, undefined, this),
54940
- view.kind === "hidden" && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(HiddenProjectList, {
54941
- hiddenIds,
54942
- onUnhide: unhide,
54943
- onBack: goHome
54944
- }, undefined, false, undefined, this),
54945
- view.kind === "project" && /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
54946
- className: "empty-state",
54947
- children: [
54948
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("div", {
54949
- className: "empty-state-title",
54950
- children: "Select a session"
54951
- }, undefined, false, undefined, this),
54952
- /* @__PURE__ */ jsx_dev_runtime22.jsxDEV("p", {
54953
- children: "Choose a conversation from the sidebar"
54954
- }, undefined, false, undefined, this)
54955
- ]
54956
- }, undefined, true, undefined, this),
54957
- view.kind === "session" && (view.presenting ? /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SessionPresentation, {
54958
- sessionId: view.session.sessionId,
54959
- project: view.project.encodedPath,
54960
- onExit: togglePresentation
54961
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SessionView, {
54962
- sessionId: view.session.sessionId,
54963
- project: view.project.encodedPath
54964
- }, undefined, false, undefined, this)),
54965
- view.kind === "subagent" && (view.presenting ? /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SubAgentPresentation, {
54966
- sessionId: view.sessionId,
54967
- project: view.project.encodedPath,
54968
- agentId: view.agentId,
54969
- onExit: togglePresentation
54970
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime22.jsxDEV(SubAgentView, {
54971
- sessionId: view.sessionId,
54972
- project: view.project.encodedPath,
54973
- agentId: view.agentId
54974
- }, undefined, false, undefined, this))
55920
+ }, undefined, true, undefined, this)
54975
55921
  ]
54976
55922
  }, undefined, true, undefined, this)
54977
55923
  ]
54978
55924
  }, undefined, true, undefined, this);
54979
55925
  }
54980
55926
  var root4 = import_client.createRoot(document.getElementById("root"));
54981
- root4.render(/* @__PURE__ */ jsx_dev_runtime22.jsxDEV(App, {}, undefined, false, undefined, this));
55927
+ root4.render(/* @__PURE__ */ jsx_dev_runtime26.jsxDEV(App, {}, undefined, false, undefined, this));