specrails-desktop 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/client/dist/assets/ActivityFeedPage-3Veccrvk.js +1 -0
  2. package/client/dist/assets/AgentsPage-2mFPghP4.js +86 -0
  3. package/client/dist/assets/{AnalyticsPage-D6LE6wG2.js → AnalyticsPage-Dyyz1ht3.js} +1 -1
  4. package/client/dist/assets/{BarChart-B366kDEj.js → BarChart-CMdLa6Es.js} +2 -2
  5. package/client/dist/assets/CodePage-D7Xwjhut.js +2 -0
  6. package/client/dist/assets/{DesktopAnalyticsPage-DG5LA_WO.js → DesktopAnalyticsPage-CTNwb639.js} +1 -1
  7. package/client/dist/assets/{DocsDialog-ChQ1oXLC.js → DocsDialog-D8yoyZDD.js} +2 -2
  8. package/client/dist/assets/{DocsPage-BfGH8NUf.js → DocsPage-CeO-fAxy.js} +2 -2
  9. package/client/dist/assets/{ExportDropdown-9tRrlfM7.js → ExportDropdown-DuoZcdYN.js} +1 -1
  10. package/client/dist/assets/{IntegrationsPage-DANIzihd.js → IntegrationsPage-iIZ0UEzf.js} +3 -3
  11. package/client/dist/assets/JobDetailPage-DgJHAH2m.js +16 -0
  12. package/client/dist/assets/JobsPage-Bv_RpRAE.js +1 -0
  13. package/client/dist/assets/code-BtsmPQLV.js +1 -0
  14. package/client/dist/assets/code-CY85RXZU.js +1 -0
  15. package/client/dist/assets/code-Coa8f2Sh.js +1 -0
  16. package/client/dist/assets/code-D1z-YDt-.js +1 -0
  17. package/client/dist/assets/code-DDU0CRS0.js +1 -0
  18. package/client/dist/assets/code-L35Loak_.js +1 -0
  19. package/client/dist/assets/code-g0qFMzyg.js +1 -0
  20. package/client/dist/assets/code-zCwBt3Uu.js +1 -0
  21. package/client/dist/assets/{dist-js-BvQ52Q67.js → dist-js-4UEGaKhD.js} +1 -1
  22. package/client/dist/assets/{dist-js-XEilFTNz.js → dist-js-H6hyhSuv.js} +1 -1
  23. package/client/dist/assets/{index-CNiaj7Sj.js → index-CGHKpC-N.js} +13 -13
  24. package/client/dist/assets/index-D17R4Cjc.css +2 -0
  25. package/client/dist/assets/{lib-DZJmnErt.js → lib-Cs5FrUJI.js} +1 -1
  26. package/client/dist/assets/{useProjectCache-H0T8Ot9j.js → useProjectCache-BZWYV-w-.js} +1 -1
  27. package/client/dist/index.html +3 -3
  28. package/package.json +1 -1
  29. package/server/dist/agent-refine-manager.js +128 -153
  30. package/server/dist/chat-manager.js +246 -0
  31. package/server/dist/code-explorer-router.js +78 -0
  32. package/server/dist/command-resolver.js +17 -0
  33. package/server/dist/contract-refine-runner.js +42 -10
  34. package/server/dist/db.js +6 -0
  35. package/server/dist/desktop-db.js +3 -0
  36. package/server/dist/explore-stdin-session.js +129 -0
  37. package/server/dist/mobile/mobile-auth.js +16 -0
  38. package/server/dist/project-router-chat.js +218 -0
  39. package/server/dist/project-router-helpers.js +275 -0
  40. package/server/dist/project-router-jobs.js +389 -0
  41. package/server/dist/project-router-settings.js +312 -0
  42. package/server/dist/project-router-setup.js +456 -0
  43. package/server/dist/project-router-spending.js +320 -0
  44. package/server/dist/project-router-terminals.js +312 -0
  45. package/server/dist/project-router-tickets.js +1767 -0
  46. package/server/dist/project-router.js +27 -3943
  47. package/server/dist/providers/claude-adapter.js +58 -17
  48. package/server/dist/providers/codex-adapter.js +6 -0
  49. package/server/dist/spawn-lifecycle.js +117 -0
  50. package/client/dist/assets/ActivityFeedPage-BupGdGjj.js +0 -1
  51. package/client/dist/assets/AgentsPage-F3xksiLd.js +0 -86
  52. package/client/dist/assets/CodePage-DLwCJgQ0.js +0 -2
  53. package/client/dist/assets/JobDetailPage-1RtejIOB.js +0 -16
  54. package/client/dist/assets/JobsPage-NuDf5Zbx.js +0 -1
  55. package/client/dist/assets/code-AL1rVIMb.js +0 -1
  56. package/client/dist/assets/code-C0BKpkht.js +0 -1
  57. package/client/dist/assets/code-C0FTS3ew.js +0 -1
  58. package/client/dist/assets/code-CPcHxzxw.js +0 -1
  59. package/client/dist/assets/code-D3ryDniw.js +0 -1
  60. package/client/dist/assets/code-D3zVVQTj.js +0 -1
  61. package/client/dist/assets/code-PCmfS3dn.js +0 -1
  62. package/client/dist/assets/code-exI0G5Wd.js +0 -1
  63. package/client/dist/assets/index-DgFfrrTX.css +0 -2
@@ -38,28 +38,46 @@ function normaliseModel(model) {
38
38
  return model || 'sonnet';
39
39
  }
40
40
  }
41
- /** Default arg block every claude spawn shares. */
41
+ /** Default arg block every claude spawn shares. `--setting-sources` is appended
42
+ * separately per-spawn (see `commonFlagsFor`) because its value depends on
43
+ * whether the caller opted into loading the user's full Claude environment. */
42
44
  const COMMON_FLAGS = [
43
45
  '--dangerously-skip-permissions',
44
46
  '--tools', 'default',
45
47
  '--output-format', 'stream-json',
46
48
  '--verbose',
47
- // Isolate app-spawned claude from the *user's* global Claude config. Without
48
- // this, the child loads ~/.claude (user CLAUDE.md memory, plugins like
49
- // claude-mem, SessionStart hooks). That bled cross-project memory into
50
- // Explore turns (e.g. an unrelated "fighting game" surfaced for a fresh
51
- // project) and inflated spec-gen tool usage past --max-turns. `project,local`
52
- // still loads the target repo's own .claude settings + CLAUDE.md, which is
53
- // exactly the context an app run should see.
54
- '--setting-sources', 'project,local',
55
49
  ];
50
+ /**
51
+ * COMMON_FLAGS + the `--setting-sources` value for this spawn.
52
+ *
53
+ * Default (`project,local`) isolates app-spawned claude from the *user's*
54
+ * global Claude config. Without this, the child loads ~/.claude (user CLAUDE.md
55
+ * memory, plugins like claude-mem, SessionStart hooks). That bled cross-project
56
+ * memory into Explore turns (e.g. an unrelated "fighting game" surfaced for a
57
+ * fresh project) and inflated spec-gen tool usage past --max-turns.
58
+ *
59
+ * When `opts.loadUserEnv` is set (the Add Spec "My approved MCPs" toggle), we
60
+ * switch to `user,project,local` so the developer's user-scope, plugin-bundled,
61
+ * and connector MCP servers are discovered. This is the ONLY way those MCP
62
+ * servers load (verified empirically against claude 2.1.177 — plugin MCP
63
+ * servers are gated by the `user` setting source); it also re-loads user
64
+ * CLAUDE.md + hooks, which is the user's explicit opt-in via the toggle.
65
+ */
66
+ function commonFlagsFor(opts) {
67
+ return [
68
+ ...COMMON_FLAGS,
69
+ '--setting-sources',
70
+ opts.loadUserEnv ? 'user,project,local' : 'project,local',
71
+ ];
72
+ }
56
73
  function buildClaudeArgs(action, opts) {
57
74
  const args = [];
58
75
  const model = normaliseModel(opts.model);
76
+ const commonFlags = commonFlagsFor(opts);
59
77
  switch (action) {
60
78
  case 'chat-turn': {
61
79
  args.push('--model', model);
62
- args.push(...COMMON_FLAGS);
80
+ args.push(...commonFlags);
63
81
  if (opts.systemPrompt)
64
82
  args.push('--system-prompt', opts.systemPrompt);
65
83
  args.push('-p', opts.prompt);
@@ -74,7 +92,7 @@ function buildClaudeArgs(action, opts) {
74
92
  throw new Error('chat-resume requires sessionId');
75
93
  }
76
94
  args.push('--model', model);
77
- args.push(...COMMON_FLAGS);
95
+ args.push(...commonFlags);
78
96
  if (opts.systemPrompt)
79
97
  args.push('--system-prompt', opts.systemPrompt);
80
98
  args.push('--resume', opts.sessionId);
@@ -85,11 +103,33 @@ function buildClaudeArgs(action, opts) {
85
103
  args.push(...opts.extraArgs);
86
104
  return args;
87
105
  }
106
+ case 'chat-stream': {
107
+ // Persistent multi-turn transport: one child stays alive and reads
108
+ // newline-delimited stream-json user messages from stdin (no `-p
109
+ // <prompt>` argument — the prompt arrives over stdin). The system prompt
110
+ // is fixed once at spawn (the Explore lightweight prompt is byte-stable,
111
+ // so this is sound). `--max-turns` is intentionally omitted: it would
112
+ // terminate the whole process after N agentic turns and end the session.
113
+ args.push('--model', model);
114
+ args.push(...commonFlags);
115
+ if (opts.systemPrompt)
116
+ args.push('--system-prompt', opts.systemPrompt);
117
+ // When the conversation already has a session (a re-spawn after idle-kill
118
+ // or crash), resume it so the persistent child restores prior context
119
+ // instead of starting a fresh thread. Absent on the very first turn.
120
+ if (opts.sessionId)
121
+ args.push('--resume', opts.sessionId);
122
+ args.push('-p');
123
+ args.push('--input-format', 'stream-json');
124
+ if (opts.extraArgs)
125
+ args.push(...opts.extraArgs);
126
+ return args;
127
+ }
88
128
  case 'rail-job': {
89
129
  // QueueManager spawns with `--append-system-prompt` (not `--system-prompt`)
90
130
  // because the slash command in the prompt brings its own system prompt;
91
131
  // we ADD to it rather than overwrite.
92
- args.push(...COMMON_FLAGS);
132
+ args.push(...commonFlags);
93
133
  args.push('--model', model);
94
134
  if (opts.systemPrompt)
95
135
  args.push('--append-system-prompt', opts.systemPrompt);
@@ -99,7 +139,7 @@ function buildClaudeArgs(action, opts) {
99
139
  return args;
100
140
  }
101
141
  case 'spec-gen': {
102
- args.push(...COMMON_FLAGS);
142
+ args.push(...commonFlags);
103
143
  args.push('--model', model);
104
144
  if (opts.maxTurns != null)
105
145
  args.push('--max-turns', String(opts.maxTurns));
@@ -113,7 +153,7 @@ function buildClaudeArgs(action, opts) {
113
153
  return args;
114
154
  }
115
155
  case 'agent-refine': {
116
- args.push(...COMMON_FLAGS);
156
+ args.push(...commonFlags);
117
157
  if (opts.sessionId)
118
158
  args.push('--resume', opts.sessionId);
119
159
  args.push('-p', opts.prompt);
@@ -123,7 +163,7 @@ function buildClaudeArgs(action, opts) {
123
163
  }
124
164
  case 'setup-enrich': {
125
165
  args.push('-p', opts.prompt);
126
- args.push(...COMMON_FLAGS);
166
+ args.push(...commonFlags);
127
167
  if (opts.extraArgs)
128
168
  args.push(...opts.extraArgs);
129
169
  return args;
@@ -133,14 +173,14 @@ function buildClaudeArgs(action, opts) {
133
173
  throw new Error('setup-enrich-resume requires sessionId');
134
174
  }
135
175
  args.push('--resume', opts.sessionId);
136
- args.push(...COMMON_FLAGS);
176
+ args.push(...commonFlags);
137
177
  args.push('-p', opts.prompt);
138
178
  if (opts.extraArgs)
139
179
  args.push(...opts.extraArgs);
140
180
  return args;
141
181
  }
142
182
  case 'auto-title': {
143
- args.push(...COMMON_FLAGS);
183
+ args.push(...commonFlags);
144
184
  args.push('-p', opts.prompt);
145
185
  return args;
146
186
  }
@@ -270,6 +310,7 @@ exports.claudeAdapter = {
270
310
  nativeOtelEnv: true,
271
311
  profileEnvSupport: true,
272
312
  systemPromptArg: true,
313
+ persistentStdin: true,
273
314
  },
274
315
  modelCatalog: () => CLAUDE_MODELS,
275
316
  defaultModel: () => 'sonnet',
@@ -98,6 +98,12 @@ function buildCodexArgs(action, opts) {
98
98
  args.push(...opts.extraArgs);
99
99
  return args;
100
100
  }
101
+ case 'chat-stream': {
102
+ // Codex has no persistent stdin multi-turn transport; the Explore
103
+ // fast-path gates on capabilities.persistentStdin, so this is never
104
+ // reached. Throw defensively rather than emit a broken argv.
105
+ throw new Error('codex does not support persistent stdin streaming (chat-stream)');
106
+ }
101
107
  case 'rail-job': {
102
108
  // Rail jobs are headless implementation pipelines. They must run repo
103
109
  // inspection, edits, tests, and git probes without interactive approval.
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ // Shared AI-CLI spawn lifecycle.
3
+ //
4
+ // Every AI-CLI invocation in the app (agent-refine, contract-refine, quick-spec,
5
+ // setup-enrich, explore chat, rail jobs) shares the same boilerplate: spawn the
6
+ // provider binary, read stdout line-by-line through `adapter.parseStreamLine`
7
+ // into an AdapterEvent[] accumulator, capture the session id, drain stderr, and
8
+ // settle once on `close` (or a wall-clock timeout / spawn error). This module
9
+ // owns exactly that core and nothing more — the genuinely-unique bits (how a
10
+ // callsite renders deltas, finalizes invocation accounting, validates output,
11
+ // manages crash-respawn / idle-kill) stay with the caller via hooks + the
12
+ // returned result. Adopting it is behaviour-preserving: the caller keeps every
13
+ // post-close decision it had, just without re-implementing the plumbing.
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.runAiCliInvocation = runAiCliInvocation;
16
+ const node_readline_1 = require("node:readline");
17
+ const cli_prompt_1 = require("./util/cli-prompt");
18
+ const STDERR_TAIL_CAP = 64 * 1024;
19
+ /**
20
+ * Run one AI-CLI invocation: spawn → stream → settle. Resolves exactly once on
21
+ * child 'close', a spawn 'error', or the optional timeout. Never rejects — a
22
+ * failure is reported via the result (`spawnFailed`/`timedOut`/`code`). The
23
+ * caller does all post-close work (finalise/record/validate) from the returned
24
+ * events.
25
+ */
26
+ function runAiCliInvocation(hooks) {
27
+ const binary = hooks.binary ?? hooks.adapter.binary;
28
+ const args = hooks.argv ?? hooks.adapter.buildArgs(hooks.action, hooks.buildOpts);
29
+ const spawn = hooks.spawn ?? cli_prompt_1.spawnAiCli;
30
+ const events = [];
31
+ let lastResultEvent = null;
32
+ let sessionId = null;
33
+ let stderrTail = '';
34
+ return new Promise((resolve) => {
35
+ let settled = false;
36
+ let timer = null;
37
+ const settle = (partial) => {
38
+ if (settled)
39
+ return;
40
+ settled = true;
41
+ if (timer) {
42
+ clearTimeout(timer);
43
+ timer = null;
44
+ }
45
+ resolve({ ...partial, events, lastResultEvent, sessionId, stderrTail });
46
+ };
47
+ let child;
48
+ try {
49
+ child = spawn(binary, args, {
50
+ env: hooks.env ?? process.env,
51
+ stdio: hooks.stdio ?? ['ignore', 'pipe', 'pipe'],
52
+ cwd: hooks.cwd,
53
+ });
54
+ }
55
+ catch (err) {
56
+ hooks.onSpawnError?.(err);
57
+ settle({ code: null, timedOut: false, spawnFailed: true, child: null });
58
+ return;
59
+ }
60
+ hooks.onSpawn?.(child);
61
+ if (hooks.timeoutMs != null) {
62
+ timer = setTimeout(() => {
63
+ hooks.onTimeout?.();
64
+ try {
65
+ child.kill('SIGTERM');
66
+ }
67
+ catch { /* already gone */ }
68
+ settle({ code: null, timedOut: true, spawnFailed: false, child });
69
+ }, hooks.timeoutMs);
70
+ }
71
+ if (child.stdout) {
72
+ const reader = (0, node_readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
73
+ if (hooks.onData)
74
+ child.stdout.on('data', () => hooks.onData('stdout'));
75
+ reader.on('line', (line) => {
76
+ hooks.onStdoutLine?.(line);
77
+ const ev = hooks.adapter.parseStreamLine(line);
78
+ if (!ev)
79
+ return;
80
+ events.push(ev);
81
+ if (ev.kind === 'session-started') {
82
+ sessionId = ev.sessionId;
83
+ }
84
+ else if (ev.kind === 'result') {
85
+ lastResultEvent = ev;
86
+ const sid = ev.payload.session_id;
87
+ if (sid)
88
+ sessionId = sid;
89
+ }
90
+ hooks.onEvent?.(ev);
91
+ });
92
+ }
93
+ if (child.stderr) {
94
+ if (hooks.onStderrLine) {
95
+ const errReader = (0, node_readline_1.createInterface)({ input: child.stderr, crlfDelay: Infinity });
96
+ if (hooks.onData)
97
+ child.stderr.on('data', () => hooks.onData('stderr'));
98
+ errReader.on('line', (line) => hooks.onStderrLine(line));
99
+ }
100
+ else {
101
+ child.stderr.on('data', (chunk) => {
102
+ if (hooks.onData)
103
+ hooks.onData('stderr');
104
+ if (stderrTail.length < STDERR_TAIL_CAP)
105
+ stderrTail += chunk.toString();
106
+ });
107
+ }
108
+ }
109
+ child.on('error', (err) => {
110
+ hooks.onSpawnError?.(err);
111
+ settle({ code: null, timedOut: false, spawnFailed: true, child });
112
+ });
113
+ child.on('close', (code) => {
114
+ settle({ code, timedOut: false, spawnFailed: false, child });
115
+ });
116
+ });
117
+ }
@@ -1 +0,0 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{K as t,Mt as n,Nt as r,St as i,_t as a,at as o,bt as s,j as c,jt as l,ot as u,qt as d,tn as f,vt as p}from"./index-CNiaj7Sj.js";var m=p(`ban`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M4.929 4.929 19.07 19.071`,key:`196cmz`}]]),h=e(f(),1);function g(e){let t=new Set;return e.filter(e=>{let n=`${e.type}:${e.jobId}`;return t.has(n)?!1:(t.add(n),!0)})}function _({activeProjectId:e,limit:t=50}){let[n,a]=(0,h.useState)([]),[o,s]=(0,h.useState)(!1),[c,u]=(0,h.useState)(!0),d=(0,h.useRef)(e);(0,h.useEffect)(()=>{d.current=e},[e]);let{registerHandler:f,unregisterHandler:p}=l();(0,h.useEffect)(()=>{if(!e){a([]),u(!0);return}let n=!1;s(!0),a([]),u(!0);async function i(){let e=r();try{let r=await fetch(`${e}/activity?limit=${t}`);if(!r.ok||n)return;let i=await r.json();if(n)return;a(i),u(i.length===t)}catch{}finally{n||s(!1)}}return i(),()=>{n=!0}},[e,t]);let m=(0,h.useCallback)(async()=>{if(o||!c)return;s(!0);let e=r();try{a(n=>{let r=n[n.length-1];if(!r)return n;let i=encodeURIComponent(r.timestamp);return fetch(`${e}/activity?limit=${t}&before=${i}`).then(e=>e.json()).then(e=>{a(t=>g([...t,...e])),u(e.length===t),s(!1)}).catch(()=>s(!1)),n})}catch{s(!1)}},[o,c,t]),_=(0,h.useCallback)(e=>{let t=e,n=d.current;if(!(!t||typeof t.type!=`string`)&&!(t.projectId&&t.projectId!==n)){if(t.type===`queue`&&Array.isArray(t.jobs)){let e=[];for(let n of t.jobs){let t=n.status===`completed`?`job_completed`:n.status===`failed`?`job_failed`:n.status===`canceled`?`job_canceled`:`job_started`;e.push({id:`${t}:${n.id}`,type:t,jobId:n.id,jobCommand:n.command??``,timestamp:n.startedAt??new Date().toISOString(),summary:`${t.replace(`_`,` `)}: ${n.command??``}`,costUsd:null})}e.length>0&&a(t=>g([...e,...t]))}if(t.type===`phase`&&t.phase&&t.state&&t.timestamp){let e={id:`phase:${t.phase}:${t.state}:${t.timestamp}`,type:`job_started`,jobId:`phase-${t.phase}`,jobCommand:i.t(`activity:feed.phaseCommand`,{phase:t.phase,state:t.state}),timestamp:t.timestamp,summary:`Phase ${t.phase} is ${t.state}`,costUsd:null};a(t=>g([e,...t]))}}},[]);return(0,h.useLayoutEffect)(()=>(f(`activity`,_),()=>p(`activity`)),[_,f,p]),{items:n,loading:o,hasMore:c,loadMore:m}}var v=n();function y(e,t){let n=Date.now()-new Date(e).getTime(),r=Math.floor(n/1e3);if(r<60)return t(`feed.relativeTime.seconds`,{value:r});let i=Math.floor(r/60);if(i<60)return t(`feed.relativeTime.minutes`,{value:i});let a=Math.floor(i/60);return a<24?t(`feed.relativeTime.hours`,{value:a}):t(`feed.relativeTime.days`,{value:Math.floor(a/24)})}function b({type:e}){switch(e){case`job_completed`:return(0,v.jsx)(u,{className:`w-4 h-4 text-green-500 flex-shrink-0`});case`job_failed`:return(0,v.jsx)(o,{className:`w-4 h-4 text-red-500 flex-shrink-0`});case`job_canceled`:return(0,v.jsx)(m,{className:`w-4 h-4 text-muted-foreground flex-shrink-0`});default:return(0,v.jsx)(c,{className:`w-4 h-4 text-blue-500 flex-shrink-0`})}}function x(e,t){switch(e){case`job_completed`:return t(`common:status.completed`);case`job_failed`:return t(`common:status.failed`);case`job_canceled`:return t(`common:status.canceled`);default:return t(`feed.typeStarted`)}}function S(e){switch(e){case`job_completed`:return`text-green-500`;case`job_failed`:return`text-red-500`;case`job_canceled`:return`text-muted-foreground`;default:return`text-blue-500`}}function C(){let{t:e}=d(`activity`),{activeProjectId:n}=s(),{items:r,loading:i,hasMore:o,loadMore:c}=_({activeProjectId:n}),l=(0,h.useRef)(null);return(0,h.useEffect)(()=>{let e=l.current;if(!e)return;let t=new IntersectionObserver(e=>{e[0].isIntersecting&&o&&!i&&c()},{threshold:.1});return t.observe(e),()=>t.disconnect()},[o,i,c]),(0,v.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,v.jsxs)(`div`,{className:`flex items-center gap-2 px-4 py-3 border-b border-border bg-background/50`,children:[(0,v.jsx)(a,{className:`w-4 h-4 text-muted-foreground`}),(0,v.jsx)(`h1`,{className:`text-sm font-medium`,children:e(`feed.title`)})]}),(0,v.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[i&&r.length===0?(0,v.jsx)(`div`,{className:`flex items-center justify-center h-32`,children:(0,v.jsx)(t,{className:`w-4 h-4 animate-spin text-muted-foreground`})}):r.length===0?(0,v.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-48 gap-2 text-muted-foreground`,children:[(0,v.jsx)(a,{className:`w-8 h-8 opacity-40`}),(0,v.jsx)(`p`,{className:`text-sm`,children:e(`feed.emptyTitle`)}),(0,v.jsx)(`p`,{className:`text-xs opacity-70`,children:e(`feed.emptyHint`)})]}):(0,v.jsx)(`ul`,{className:`divide-y divide-border/50`,children:r.map(t=>(0,v.jsxs)(`li`,{className:`flex items-center gap-3 px-4 py-2.5 hover:bg-accent/30 transition-colors`,children:[(0,v.jsx)(b,{type:t.type}),(0,v.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,v.jsx)(`p`,{className:`text-xs text-foreground truncate`,title:t.jobCommand,children:t.jobCommand}),(0,v.jsxs)(`div`,{className:`flex items-center gap-2 mt-0.5`,children:[(0,v.jsx)(`span`,{className:`text-xs font-medium ${S(t.type)}`,children:x(t.type,e)}),t.costUsd!=null&&(0,v.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`$`,t.costUsd.toFixed(4)]})]})]}),(0,v.jsx)(`span`,{className:`text-xs text-muted-foreground flex-shrink-0 tabular-nums`,children:y(t.timestamp,e)})]},`${t.type}:${t.jobId}`))}),(0,v.jsx)(`div`,{ref:l,className:`h-1`}),i&&r.length>0&&(0,v.jsx)(`div`,{className:`flex justify-center py-3`,children:(0,v.jsx)(t,{className:`w-4 h-4 animate-spin text-muted-foreground`})}),!o&&r.length>0&&(0,v.jsx)(`p`,{className:`text-center text-xs text-muted-foreground py-3`,children:e(`feed.allLoaded`)})]})]})}export{C as default};