@njeybe/nativ 2.7.0 → 2.9.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 (97) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +26 -1
  3. package/dist/commands/check.d.ts +3 -0
  4. package/dist/commands/check.js +36 -0
  5. package/dist/commands/doctor.js +7 -1
  6. package/dist/commands/init.d.ts +1 -0
  7. package/dist/commands/init.js +1 -1
  8. package/dist/commands/setup.d.ts +1 -0
  9. package/dist/commands/setup.js +1 -0
  10. package/dist/commands/task/task-add.js +49 -6
  11. package/dist/commands/task/task-common.d.ts +6 -0
  12. package/dist/commands/task/task-lifecycle.d.ts +3 -1
  13. package/dist/commands/task/task-lifecycle.js +40 -2
  14. package/dist/commands/update.d.ts +1 -0
  15. package/dist/commands/update.js +1 -1
  16. package/dist/commands/verify.d.ts +7 -0
  17. package/dist/commands/verify.js +26 -2
  18. package/dist/core/checks/docs-check.d.ts +18 -0
  19. package/dist/core/checks/docs-check.js +186 -0
  20. package/dist/core/code-shape.d.ts +7 -2
  21. package/dist/core/code-shape.js +46 -16
  22. package/dist/core/env-reads.d.ts +15 -0
  23. package/dist/core/env-reads.js +126 -0
  24. package/dist/core/human-gate.js +6 -3
  25. package/dist/core/profiles.d.ts +8 -0
  26. package/dist/core/profiles.js +43 -2
  27. package/dist/core/root-resolver.d.ts +4 -0
  28. package/dist/core/root-resolver.js +32 -0
  29. package/dist/core/setup/gitignore-block.d.ts +17 -0
  30. package/dist/core/setup/gitignore-block.js +182 -0
  31. package/dist/core/setup/pm-playbook.d.ts +1 -1
  32. package/dist/core/setup/pm-playbook.js +1 -1
  33. package/dist/core/setup/setup-planner.js +36 -3
  34. package/dist/core/setup/types.d.ts +6 -0
  35. package/dist/core/verifier/clean-tree.d.ts +14 -0
  36. package/dist/core/verifier/clean-tree.js +151 -0
  37. package/dist/core/verifier/executor.d.ts +3 -1
  38. package/dist/core/verifier/executor.js +54 -31
  39. package/dist/core/verifier/isolation.d.ts +25 -0
  40. package/dist/core/verifier/isolation.js +77 -0
  41. package/dist/core/verifier/portability-lint.d.ts +2 -0
  42. package/dist/core/verifier/portability-lint.js +64 -0
  43. package/dist/core/verifier/skip-policy.d.ts +8 -0
  44. package/dist/core/verifier/skip-policy.js +13 -0
  45. package/dist/core/verifier/task-verifier.d.ts +6 -8
  46. package/dist/core/verifier/task-verifier.js +68 -39
  47. package/dist/core/verifier/types.d.ts +4 -0
  48. package/dist/core/verifier/verify-env.d.ts +2 -0
  49. package/dist/core/verifier/verify-env.js +19 -0
  50. package/dist/index.js +33 -4
  51. package/dist/mcp/server.js +9 -2
  52. package/dist/scanner/types.d.ts +4 -0
  53. package/dist/scanner/types.js +9 -0
  54. package/dist/server/routes/task-routes.d.ts +1 -0
  55. package/dist/server/studio-ui/pages/benchmarks/script.js +9 -8
  56. package/dist/server/studio-ui/pages/benchmarks/styles.js +3 -1
  57. package/dist/server/studio-ui/pages/database/browser.js +1 -1
  58. package/dist/server/studio-ui/pages/database/connection.js +5 -4
  59. package/dist/server/studio-ui/pages/database/data-shell.js +8 -13
  60. package/dist/server/studio-ui/pages/database/drift.js +1 -1
  61. package/dist/server/studio-ui/pages/database/events.js +1 -2
  62. package/dist/server/studio-ui/pages/database/explorer.js +4 -4
  63. package/dist/server/studio-ui/pages/flow/layout.js +1 -1
  64. package/dist/server/studio-ui/pages/flow/styles.js +1 -1
  65. package/dist/server/studio-ui/pages/flow/timeline-view.js +1 -1
  66. package/dist/server/studio-ui/pages/home/script.js +26 -29
  67. package/dist/server/studio-ui/pages/home/styles.js +2 -6
  68. package/dist/server/studio-ui/pages/tasks/script.js +25 -40
  69. package/dist/server/studio-ui/pages/tasks/styles.js +9 -9
  70. package/dist/server/studio-ui/pages/team/script.js +27 -8
  71. package/dist/server/studio-ui/pages/team/styles.js +0 -1
  72. package/dist/server/studio-ui/shell/console.js +13 -7
  73. package/dist/server/studio-ui/shell/detail-view.js +3 -3
  74. package/dist/server/studio-ui/shell/dispatch.js +9 -8
  75. package/dist/server/studio-ui/shell/events-tools.js +11 -0
  76. package/dist/server/studio-ui/shell/format.js +31 -2
  77. package/dist/server/studio-ui/shell/markup-head.js +1 -1
  78. package/dist/server/studio-ui/shell/markup-tools.js +13 -8
  79. package/dist/server/studio-ui/shell/model.js +8 -2
  80. package/dist/server/studio-ui/shell/paint.js +1 -3
  81. package/dist/server/studio-ui/shell/pipeline-state.js +2 -11
  82. package/dist/server/studio-ui/shell/pulse.js +8 -7
  83. package/dist/server/studio-ui/shell/sidebar-view.js +2 -3
  84. package/dist/server/studio-ui/shell/triage.js +1 -1
  85. package/dist/server/studio-ui/styles/console.js +3 -0
  86. package/dist/server/studio-ui/styles/shell.js +4 -6
  87. package/package.json +1 -1
  88. package/plugin/.claude-plugin/plugin.json +1 -1
  89. package/plugin/agents/architect.md +15 -8
  90. package/plugin/agents/verifier.md +16 -11
  91. package/plugin/skills/nativ/SKILL.md +18 -15
  92. package/templates/AGENTS.md +18 -15
  93. package/templates/GEMINI.md +1 -1
  94. package/templates/claude-agents/architect.md +15 -8
  95. package/templates/claude-agents/verifier.md +16 -11
  96. package/templates/dot-ai/subagents/architect.md +2 -2
  97. package/templates/dot-ai/subagents/qa-tester.md +14 -13
@@ -34,9 +34,9 @@ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic ben
34
34
  if (env.cpuCount) meta.push(env.cpuCount + ' CPUs');
35
35
  var score = clampPct(s.score);
36
36
  return '<section class="card hero' + (running ? ' is-running' : '') + '" aria-labelledby="hero-h" aria-busy="' + running + '">' +
37
- '<div><h2 class="eyebrow" id="hero-h">Average throughput</h2>' +
37
+ '<div><h2 class="hero-title" id="hero-h">Average throughput</h2>' +
38
38
  '<div class="hero-value">' + num(Math.round(s.averageThroughputOpsPerSec || 0)) + '<span class="badge badge-accent">ops/sec</span></div>' +
39
- '<p class="hero-meta">' + meta.join(' · ') + '</p></div>' +
39
+ '<p class="hero-meta">' + meta.join(', ') + '</p></div>' +
40
40
  '<dl class="hero-stats">' +
41
41
  '<div><dt>Score</dt><dd class="' + (score < 100 ? 'text-danger' : '') + '">' + score + '%</dd></div>' +
42
42
  '<div><dt>Scenarios passed</dt><dd>' + num(s.passedScenarios) + '/' + num(s.totalScenarios) + '</dd></div>' +
@@ -48,16 +48,17 @@ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic ben
48
48
  function scenarioCards(r) {
49
49
  var byId = {}, known = {};
50
50
  (r.scenarios || []).forEach(function (s) { byId[s.id] = s; });
51
- var cards = SCENARIOS.map(function (meta) { known[meta.id] = true; return scenarioCard(meta.label, byId[meta.id]); });
51
+ var cards = [], idle = [];
52
+ SCENARIOS.forEach(function (meta) {
53
+ known[meta.id] = true;
54
+ if (byId[meta.id]) cards.push(scenarioCard(meta.label, byId[meta.id])); else idle.push(meta.label);
55
+ });
52
56
  (r.scenarios || []).forEach(function (s) { if (!known[s.id]) cards.push(scenarioCard(s.name || s.id, s)); });
57
+ if (idle.length) cards.push('<p class="muted not-run">Not run: ' + idle.map(esc).join(', ') + '.</p>');
53
58
  return cards.join('');
54
59
  }
55
60
 
56
61
  function scenarioCard(label, s) {
57
- if (!s) {
58
- return '<article class="card scenario is-idle"><header class="scenario-head"><h3>' + esc(label) + '</h3><span class="badge badge-neutral">Not run</span></header>' +
59
- '<p class="muted">Not part of the last run. Run the full matrix to score it.</p></article>';
60
- }
61
62
  var total = (s.assertionsPassed || 0) + (s.assertionsFailed || 0);
62
63
  var passed = s.status === 'passed';
63
64
  var score = total ? Math.round((s.assertionsPassed || 0) / total * 100) : (passed ? 100 : 0);
@@ -76,7 +77,7 @@ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic ben
76
77
  '<div class="metrics"><div><span class="metric-v">' + score + '%</span><span class="metric-l">Score</span></div>' +
77
78
  '<div><span class="metric-v">' + fmtMs(s.durationMs) + '</span><span class="metric-l">Latency</span></div>' +
78
79
  '<div><span class="metric-v">' + num(Math.round(s.throughputOpsPerSec || 0)) + '</span><span class="metric-l">ops/sec</span></div></div>' +
79
- progressBar(score, 'sc-' + s.id, label + ' score', passed ? 'success' : 'danger') +
80
+ progressBar(score, 'sc-' + s.id, label + ' score', passed ? '' : 'danger') +
80
81
  (details ? '<dl class="details">' + details + '</dl>' : '') + errors + '</article>';
81
82
  }
82
83
 
@@ -2,7 +2,9 @@
2
2
  export const benchmarksCss = String.raw `/* Benchmarks */
3
3
  .hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 20px 32px; padding: 22px 0; margin-bottom: 0; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; box-shadow: none; transition: opacity .2s; }
4
4
  .hero.is-running { opacity: .6; }
5
- .eyebrow { margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
5
+ .hero-title { margin: 0; font-size: 14px; font-weight: 600; color: var(--muted); }
6
+ .not-run { margin: 0; padding: 12px 0; font-size: 13px; }
7
+ .scenario .progress { height: 4px; }
6
8
  .hero-value { display: flex; align-items: center; gap: 10px; margin: 6px 0; font-size: 40px; font-weight: 650; letter-spacing: -0.03em; line-height: 1.1; font-variant-numeric: tabular-nums; }
7
9
  .hero-value .badge { font-size: 12px; letter-spacing: 0; }
8
10
  .hero-meta { margin: 0; font-size: 13.5px; color: var(--muted); }
@@ -168,7 +168,7 @@ export const databaseBrowserScript = String.raw ` // ─── Database Studio:
168
168
  table.columns.forEach(function (col) {
169
169
  var isPk = col.primaryKey;
170
170
  var fieldLabel = esc(col.name) + (isPk ? ' (Primary Key - Optional/Auto)' : '');
171
- var placeholder = col.type + (col.default ? ' · default: ' + col.default : '');
171
+ var placeholder = col.type + (col.default ? ', default: ' + col.default : '');
172
172
  var isLong = /json|map|array|text/i.test(col.type);
173
173
  if (isLong) {
174
174
  html += '<div class="field field-full"><label><b>' + fieldLabel + '</b></label><textarea class="input" name="' + esc(col.name) + '" placeholder="' + esc(placeholder) + '"></textarea></div>';
@@ -3,10 +3,11 @@ export const databaseConnectionScript = String.raw ` // ─── Database Stud
3
3
  function renderModalStatus() {
4
4
  ['dev', 'prod'].forEach(function (env) {
5
5
  var s = state.status && state.status[env];
6
- $('mdot-' + env).className = 'dot ' + (s && s.connected ? 'on' : 'off');
6
+ var unset = !s || /No (Dev|Prod) database configured/.test(s.error || '');
7
+ $('mdot-' + env).className = 'dot ' + (s && s.connected ? 'on' : unset ? '' : 'off');
7
8
  $('current-' + env).innerHTML = s && s.maskedUrl
8
9
  ? 'Current: <code>' + esc(s.maskedUrl) + '</code><span class="copy-wrap"><span class="tooltip">Copied!</span><button class="btn small" type="button" data-copy-masked="' + env + '">Copy</button></span>'
9
- : 'Current: <span class="muted">not configured</span>';
10
+ : 'Current: <span class="muted">Not set up</span>';
10
11
  });
11
12
  }
12
13
 
@@ -139,8 +140,8 @@ export const databaseConnectionScript = String.raw ` // ─── Database Stud
139
140
  if (r.success) {
140
141
  var count = r.entityCount != null ? r.entityCount : r.tableCount;
141
142
  out.className = 'result ok';
142
- out.textContent = 'Connected to ' + (ENGINE_LABEL[r.engine] || r.engine) + ' in ' + Math.round(r.pingMs) + 'ms · ' + count + ' ' +
143
- entityNoun({ engine: r.engine }, true).toLowerCase() + ' · saved to session as ' + r.maskedUrl;
143
+ out.textContent = 'Connected to ' + (ENGINE_LABEL[r.engine] || r.engine) + ' in ' + Math.round(r.pingMs) + 'ms, ' + count + ' ' +
144
+ entityNoun({ engine: r.engine }, true).toLowerCase() + ', saved to session as ' + r.maskedUrl;
144
145
  $('url-' + env).value = '';
145
146
  $('ppass-' + env).value = '';
146
147
  return true;
@@ -53,9 +53,12 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
53
53
  var label = env.toUpperCase();
54
54
  if (!s) return '<span class="pill"><span class="dot"></span><b>' + label + ':</b> loading…</span>';
55
55
  if (!s.connected) {
56
- var why = /No (Dev|Prod) database configured/.test(s.error || '') ? 'not configured' : 'offline';
57
- return '<span class="pill" title="' + esc(s.error || '') + '"><span class="dot off"></span><b>' + label + ':</b> ' + why +
58
- ' <span class="badge badge-offline">OFFLINE</span></span>';
56
+ if (/No (Dev|Prod) database configured/.test(s.error || '')) {
57
+ return '<span class="pill"><span class="dot"></span><b>' + label + ':</b> ' +
58
+ '<span class="badge badge-neutral">Not set up</span></span>';
59
+ }
60
+ return '<span class="pill" title="' + esc(s.error || '') + '"><span class="dot off"></span><b>' + label + ':</b> offline' +
61
+ ' <span class="badge badge-offline">Offline</span></span>';
59
62
  }
60
63
  var sourceBadge = s.synthesized && s.sourceKey
61
64
  ? ' <span class="badge badge-synth" title="Assembled in memory from fragmented .env keys">synthesized from ' + esc(keyFamily(s.sourceKey)) + '</span>'
@@ -63,7 +66,7 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
63
66
  ? ' <span class="badge badge-source" title="Detected in ' + esc(s.exampleFile || '.env.example') + '">via ' + esc(s.sourceKey) + '</span>'
64
67
  : (s.sourceKey ? ' <span class="badge badge-source">via ' + esc(s.sourceKey) + '</span>' : '');
65
68
  return '<span class="pill" title="' + esc(s.maskedUrl) + '"><span class="dot on"></span><b>' + label + ':</b> ' +
66
- esc(ENGINE_LABEL[s.engine] || s.engine) + (s.database ? ' · ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms): ' +
69
+ esc(ENGINE_LABEL[s.engine] || s.engine) + (s.database ? ', ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms): ' +
67
70
  entityCount(s) + ' ' + entityNoun(s, true) + sourceBadge + ' <span class="badge badge-online">ONLINE</span></span>';
68
71
  }
69
72
 
@@ -97,15 +100,8 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
97
100
  '<span class="diagnostic-tag">[DIAGNOSTIC]</span> ' +
98
101
  '<span class="diagnostic-msg">' + esc(suggestion) + '</span>' +
99
102
  '</div>' +
100
- '<div class="diagnostic-actions">' +
101
- '<button class="btn small" type="button" id="btn-diag-settings">Open Connection Settings</button>' +
102
- '</div>' +
103
103
  '</div>';
104
104
  bannerEl.hidden = false;
105
- var btnDiag = $('btn-diag-settings');
106
- if (btnDiag) {
107
- btnDiag.onclick = function () { openSettings(); };
108
- }
109
105
  } else {
110
106
  bannerEl.innerHTML = '';
111
107
  bannerEl.hidden = true;
@@ -158,8 +154,7 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
158
154
  '<path d="M128,24C74.17,24,32,48.6,32,80v96c0,31.4,42.17,56,96,56s96-24.6,96-56V80C224,48.6,181.83,24,128,24Zm80,104c0,9.62-7.88,19.43-21.61,26.92C170.93,163.35,150.19,168,128,168s-42.93-4.65-58.39-13.08C55.88,147.43,48,137.62,48,128V111.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64ZM69.61,53.08C85.07,44.65,105.81,40,128,40s42.93,4.65,58.39,13.08C200.12,60.57,208,70.38,208,80s-7.88,19.43-21.61,26.92C170.93,115.35,150.19,120,128,120s-42.93-4.65-58.39-13.08C55.88,99.43,48,89.62,48,80S55.88,60.57,69.61,53.08ZM186.39,202.92C170.93,211.35,150.19,216,128,216s-42.93-4.65-58.39-13.08C55.88,195.43,48,185.62,48,176V159.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64V176C208,185.62,200.12,195.43,186.39,202.92Z"/></svg>';
159
155
 
160
156
  function emptyState(title, text) {
161
- return '<div class="empty card">' + DB_ICON + '<h3>' + esc(title) + '</h3><p>' + esc(text) + '</p>' +
162
- '<button class="btn primary" type="button" data-action="open-settings">Connection Settings</button></div>';
157
+ return '<div class="empty card">' + DB_ICON + '<h3>' + esc(title) + '</h3><p>' + esc(text) + '</p></div>';
163
158
  }
164
159
 
165
160
  `;
@@ -292,7 +292,7 @@ export const databaseDriftScript = String.raw ` // ─── Database Studio: d
292
292
  var lang = state.scriptLang;
293
293
  var unit = lang === 'json' ? ' change(s)' : ' statement(s)';
294
294
  return '<div class="sql-wrap card"><div class="sql-head"><span class="muted"><span class="lang">' + LANG_LABEL[lang] + '</span>' +
295
- (destructive ? '<span class="badge badge-dropped">DESTRUCTIVE</span> ' : '') + state.scriptCount + unit + ' · read-only preview</span>' +
295
+ (destructive ? '<span class="badge badge-dropped">DESTRUCTIVE</span> ' : '') + state.scriptCount + unit + ', read-only preview</span>' +
296
296
  '<span class="copy-wrap"><span class="tooltip">Copied!</span><button class="btn small" type="button" data-action="copy-sql">Copy ' +
297
297
  (lang === 'sql' ? 'SQL' : lang === 'js' ? 'Script' : 'JSON') + '</button></span></div>' +
298
298
  '<pre class="sql" tabindex="0" aria-label="Migration script (' + LANG_LABEL[lang] + ')"><code>' + highlightScript(state.sql, lang) + '</code></pre></div>';
@@ -123,8 +123,7 @@ export const databaseEventsScript = String.raw ` // ─── Events: Database
123
123
  return;
124
124
  }
125
125
  var action = el.getAttribute('data-action');
126
- if (action === 'open-settings') openSettings();
127
- else if (action === 'copy-sql') copyText(state.sql, el);
126
+ if (action === 'copy-sql') copyText(state.sql, el);
128
127
  else if (action === 'edit-record') openEditRecordModal(Number(el.getAttribute('data-row')));
129
128
  else if (action === 'delete-record') handleDeleteRecord(Number(el.getAttribute('data-row')));
130
129
  else if (action === 'preview-url') {
@@ -45,7 +45,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
45
45
  : '';
46
46
  var docMeta = coll && (t.documentCount != null || t.sampledDocuments != null)
47
47
  ? '<p class="desc">' + (t.documentCount != null ? '~' + t.documentCount + ' documents' : 'Document count unavailable') +
48
- (t.sampledDocuments != null ? ' · fields inferred from ' + t.sampledDocuments + ' sampled' : '') + '</p>'
48
+ (t.sampledDocuments != null ? ', fields inferred from ' + t.sampledDocuments + ' sampled' : '') + '</p>'
49
49
  : '';
50
50
  return '<details class="table-card"><summary><span class="tname">' + esc(t.name) + '</span>' +
51
51
  (coll ? '<span class="badge badge-collection">COLLECTION</span>' : '') +
@@ -87,7 +87,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
87
87
  var parts = [m.mimeType || 'unknown type'];
88
88
  if (m.width && m.height) parts.push(m.width + '×' + m.height);
89
89
  if (m.sizeBytes != null) parts.push(formatBytes(m.sizeBytes));
90
- return parts.join(' · ');
90
+ return parts.join(', ');
91
91
  }
92
92
 
93
93
  function showThumb(el) {
@@ -117,7 +117,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
117
117
  hideThumb();
118
118
  var m = entry.media;
119
119
  $('media-title').textContent = entry.table + '.' + entry.field;
120
- $('media-sub').textContent = (m ? SOURCE_LABEL[m.source] || m.source : 'Image field') + ' · ' + entry.type;
120
+ $('media-sub').textContent = (m ? SOURCE_LABEL[m.source] || m.source : 'Image field') + ', ' + entry.type;
121
121
  $('media-thumb').innerHTML = thumbHtml(m);
122
122
  var rows = [];
123
123
  if (m) {
@@ -144,7 +144,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
144
144
  var body;
145
145
  if (!s || !s.connected) {
146
146
  body = '<div class="empty" style="padding:28px 12px"><p>' + esc(s && s.error ? s.error : 'No database connected.') + '</p>' +
147
- '<button class="btn small" type="button" data-action="open-settings">Connect</button></div>';
147
+ '<p class="muted">Use Connection Settings at the top.</p></div>';
148
148
  } else if (!tables.length) {
149
149
  body = '<p class="muted">Connected, but this database has no ' + entityNoun(s, true).toLowerCase() + ' yet.</p>';
150
150
  } else if (!filtered.length) {
@@ -1,5 +1,5 @@
1
1
  // Plan layout: rounds, lanes, links and the highlight set. Pure, no DOM.
2
- export const flowLayoutScript = String.raw ` var FLOW = { W: 206, H: 58, GX: 52, GY: 10, PAD: 14, LX: 128, TOP: 30 };
2
+ export const flowLayoutScript = String.raw ` var FLOW = { W: 206, H: 74, GX: 52, GY: 10, PAD: 14, LX: 128, TOP: 30 };
3
3
 
4
4
  function flowAgentKey(t) { return t.agent || 'unassigned'; }
5
5
 
@@ -25,7 +25,7 @@ export const flowCss = String.raw `#panel-flow .seg { display: inline-flex; bord
25
25
  #panel-flow .node .l1 { display: flex; align-items: center; gap: 6px; }
26
26
  #panel-flow .node .l1 .d { margin-left: auto; font: 11px var(--mono); color: var(--fg-3); }
27
27
  #panel-flow .node .l1 .mono { font-size: 11px; }
28
- #panel-flow .node .title { font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
28
+ #panel-flow .node .title { font-size: 12.5px; line-height: 1.3; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
29
29
  #panel-flow .canvas.focus .node:not(.hot) { opacity: .3; }
30
30
  #panel-flow .node.sel { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
31
31
  #panel-flow .legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--fg-2); align-items: center; }
@@ -51,7 +51,7 @@ export const flowTimelineScript = String.raw ` function flowStat(k, v, s) {
51
51
  var cw = t.checkMs && t.status === 'completed' ? Math.min(w, t.checkMs * tm.sg.k) : 0;
52
52
  var lx = w > 64 ? x1 + 6 : x2 + 4;
53
53
  var took = (t.status === 'in_progress' ? 'running' : 'took ' + fmt.dur(t.durationMs)) + (t.checkMs ? ', check ' + fmt.dur(t.checkMs) : '');
54
- var tip = ui.esc(t.id + ' · ' + t.title) + '&#10;' + ui.esc(took);
54
+ var tip = ui.esc(t.id + ', ' + t.title) + '&#10;' + ui.esc(took);
55
55
  return '<g><rect class="bar-w ' + ui.esc(t.status) + '" data-task="' + ui.esc(t.id) + '" data-row="' + row + '" x="' + x1 + '" y="' + by +
56
56
  '" width="' + w + '" height="21" rx="4"><title>' + tip + '</title></rect>' +
57
57
  (cw > 1 ? '<rect class="bar-c" x="' + (x2 - cw) + '" y="' + (by + 17) + '" width="' + cw + '" height="4" rx="1"/>' : '') +
@@ -24,7 +24,7 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
24
24
  var t = item.task;
25
25
  if (t.status === 'blocked') return t.reason || 'Paused after 3 attempts.';
26
26
  var e = item.escalation || {};
27
- return e.reason || e.summary || e.details || 'Waiting for your decision.';
27
+ return e.reason || e.summary || e.details || 'Needs your decision.';
28
28
  }
29
29
 
30
30
  function needActions(item) {
@@ -55,16 +55,8 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
55
55
  rightText(count) + '</div>' + n.map(needItem).join('') + '</section>';
56
56
  }
57
57
 
58
- function isReady(model, t) {
59
- if (t.status !== 'pending') return false;
60
- return t.isAvailable || t.deps.every(function (d) {
61
- var dep = model.byId[d];
62
- return !dep || dep.status === 'completed';
63
- });
64
- }
65
-
66
58
  function readyTasks(model) {
67
- return model.tasks.filter(function (t) { return isReady(model, t); });
59
+ return model.tasks.filter(Studio.status.isReady);
68
60
  }
69
61
 
70
62
  function dispatchBtn(id, label, cls) {
@@ -75,17 +67,18 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
75
67
  function taskCells(t) {
76
68
  return '<span class="mono id">' + esc(t.id) + '</span>' +
77
69
  '<button type="button" class="t link" data-task="' + esc(t.id) + '">' + esc(t.title) + '</button>' +
78
- '<span class="agent">' + esc(t.agent || '') + '</span>';
70
+ '<span class="agent">' + esc(Studio.agentInfo(t.agent).label) + '</span>';
79
71
  }
80
72
 
81
73
  function runRow(t) {
82
74
  var since = t.startT ? ' data-since="' + t.startT + '">' + Studio.fmt.dur(Studio.now() - t.startT) : '>';
83
75
  var row = '<div class="row run-row">' + taskCells(t) +
84
- '<span class="state"><i class="dot run"></i>Running</span>' +
76
+ '<span class="state">Running</span>' +
85
77
  '<span class="mono num timer"' + since + '</span></div>';
86
78
  var log = '<span class="mono log">' + esc(t.latestLog || '') + '</span>';
87
- return row + '<div class="row sub-row">' + log + '<button type="button" class="btn" data-act="home-console"' +
88
- ' data-id="' + esc(t.id) + '">Open console</button></div>';
79
+ var watch = t.run ? '<button type="button" class="btn" data-act="home-console" data-id="' +
80
+ esc(t.id) + '">Watch live</button>' : '';
81
+ return row + '<div class="row sub-row">' + log + watch + '</div>';
89
82
  }
90
83
 
91
84
  function nowRunning(model) {
@@ -96,23 +89,18 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
96
89
  return section('running', 'Now running', '', '<div class="row calm"><span>No agent is running.</span>' + btn + '</div>');
97
90
  }
98
91
 
99
- function waitOn(model, t) {
100
- var open = t.deps.filter(function (d) { var x = model.byId[d]; return x && x.status !== 'completed'; });
101
- return open[0] || '';
102
- }
103
-
104
- function nextRow(model, t) {
105
- var end = isReady(model, t) ? dispatchBtn(t.id, 'Dispatch') :
106
- '<span class="wait">waits <span class="mono">' + esc(waitOn(model, t)) + '</span></span>';
92
+ function nextRow(t) {
93
+ var end = Studio.status.isReady(t) ? dispatchBtn(t.id, 'Dispatch') :
94
+ '<span class="wait">' + esc(Studio.status.waitText(t)) + '</span>';
107
95
  return '<div class="row next-row">' + taskCells(t) + end + '</div>';
108
96
  }
109
97
 
110
98
  function upNext(model) {
111
99
  var list = model.tasks.filter(function (t) { return t.status === 'pending'; });
112
- var ready = list.filter(function (t) { return isReady(model, t); });
113
- var waiting = list.filter(function (t) { return !isReady(model, t); });
100
+ var ready = list.filter(Studio.status.isReady);
101
+ var waiting = list.filter(function (t) { return !Studio.status.isReady(t); });
114
102
  var rows = ready.concat(waiting).slice(0, UP_NEXT_CAP);
115
- var body = rows.length ? rows.map(function (t) { return nextRow(model, t); }).join('') :
103
+ var body = rows.length ? rows.map(nextRow).join('') :
116
104
  '<div class="row calm">Nothing is waiting.</div>';
117
105
  return section('next', 'Up next', '', body);
118
106
  }
@@ -127,18 +115,27 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
127
115
  return section('milestone', 'Milestone', '', body);
128
116
  }
129
117
 
118
+ // One event per task that has started or finished; the state word comes from its status now.
130
119
  function activityEvents(model) {
131
120
  var out = [];
132
121
  model.tasks.forEach(function (t) {
133
- if (t.endT) out.push({ at: t.endT, id: t.id, what: 'completed' });
134
- if (t.startT) out.push({ at: t.startT, id: t.id, what: 'started' });
122
+ var at = t.status === 'completed' ? (t.endT || t.startT) : t.startT;
123
+ if (at) out.push({ at: at, task: t });
135
124
  });
136
125
  return out.sort(function (a, b) { return b.at - a.at; }).slice(0, ACTIVITY_CAP);
137
126
  }
138
127
 
128
+ function activityState(t) {
129
+ var word = Studio.status.word(Studio.status.bucket(t));
130
+ var took = t.status === 'completed' && t.durationMs ? ', took ' + Studio.fmt.dur(t.durationMs) : '';
131
+ return word + took;
132
+ }
133
+
139
134
  function activityRow(e) {
140
- return '<div class="row act-row" data-event="' + esc(e.id) + '"><span class="mono num when">' +
141
- Studio.ui.when(e.at) + '</span><span class="mono id">' + esc(e.id) + '</span><span>' + e.what + '</span></div>';
135
+ var t = e.task;
136
+ return '<div class="row act-row" data-event="' + esc(t.id) + '"><span class="mono num when">' +
137
+ Studio.ui.when(e.at) + '</span><button type="button" class="t link" data-task="' + esc(t.id) + '">' +
138
+ esc(t.title) + '</button><span class="state">' + esc(activityState(t)) + '</span></div>';
142
139
  }
143
140
 
144
141
  function recentActivity(model) {
@@ -1,20 +1,17 @@
1
1
  // Home page styles, all scoped under #panel-home. Ink: flat sections, one raised decision surface.
2
- export const homeCss = String.raw `#panel-home .home { max-width: 960px; display: flex; flex-direction: column; }
2
+ export const homeCss = String.raw `#panel-home .home { display: flex; flex-direction: column; }
3
3
  #panel-home .page-head h1 { font-size: 22px; font-weight: 600; margin: 0 0 12px; letter-spacing: -.01em; }
4
4
  #panel-home .home-sec { border-top: 1px solid var(--line); padding: 12px 0; min-width: 0; }
5
5
  #panel-home .sec-head { display: flex; align-items: center; gap: 8px; min-height: 28px; }
6
6
  #panel-home .sec-head h2 { font-size: 13px; font-weight: 600; margin: 0; color: var(--fg-2); }
7
7
  #panel-home .sec-head .right { margin-left: auto; font-size: 12px; color: var(--fg-3); }
8
8
  #panel-home .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
9
- #panel-home .row { display: flex; align-items: center; gap: 12px; min-height: 36px; border-bottom: 1px solid var(--line); }
10
- #panel-home .row:last-child { border-bottom: 0; }
9
+ #panel-home .row { display: flex; align-items: center; gap: 12px; min-height: 36px; }
11
10
  #panel-home .row .id { width: 72px; flex: none; color: var(--fg-2); font-size: 12px; }
12
11
  #panel-home .row .t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
13
12
  #panel-home .link { background: none; border: 0; padding: 0; text-align: left; color: var(--fg); cursor: pointer; font: inherit; }
14
13
  #panel-home .agent { width: 96px; flex: none; color: var(--fg-2); font-size: 12.5px; }
15
14
  #panel-home .state { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
16
- #panel-home .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-3); }
17
- #panel-home .dot.run { background: var(--run); }
18
15
  #panel-home .timer { width: 64px; text-align: right; font-size: 12px; }
19
16
  #panel-home .sub-row .log { flex: 1; min-width: 0; font-size: 12px; color: var(--fg-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
20
17
  #panel-home .wait { color: var(--fg-3); font-size: 12.5px; }
@@ -30,7 +27,6 @@ export const homeCss = String.raw `#panel-home .home { max-width: 960px; display
30
27
  #panel-home .act-row .when { width: 72px; flex: none; font-size: 12px; color: var(--fg-3); }
31
28
  #panel-home .skel-row .skel { height: 12px; width: 100%; border-radius: 6px; background: var(--surface-2); }
32
29
  @media (max-width: 1024px) {
33
- #panel-home .home { max-width: none; }
34
30
  #panel-home .agent { display: none; }
35
31
  }
36
32
  `;
@@ -3,11 +3,7 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
3
3
  var CAP = 5;
4
4
  var view = { lastMs: null, showAll: false, open: {}, mode: null, filter: 'all', groups: {} };
5
5
  var KEY = 'nativ-studio:tasks-view';
6
- var FILTERS = [['all', 'All'], ['running', 'Running'], ['ready', 'Ready'], ['blocked', 'Blocked'],
7
- ['done', 'Done']];
8
- var DOT = { completed: 'ok', in_progress: 'run', blocked: 'bad', ready: 'fg', waiting: 'faint' };
9
- var LABEL = { completed: 'Done', in_progress: 'Running', blocked: 'Blocked', ready: 'Ready',
10
- waiting: 'Waiting' };
6
+ var FILTERS = [['all', 'All']].concat(Studio.status.buckets);
11
7
 
12
8
  function readMode() {
13
9
  try { return localStorage.getItem(KEY) === 'board' ? 'board' : 'list'; } catch (e) { return 'list'; }
@@ -16,30 +12,15 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
16
12
  try { localStorage.setItem(KEY, mode); } catch (e) { /* private mode */ }
17
13
  }
18
14
 
19
- // Same notion of ready as Home and the sidebar: pending and every dependency done.
20
- function isReady(model, t) {
21
- if (t.status !== 'pending') return false;
22
- return t.isAvailable || t.deps.every(function (d) {
23
- var dep = model.byId && model.byId[d];
24
- return !dep || dep.status === 'completed';
25
- });
26
- }
27
- function kindOf(model, t) {
28
- if (t.status === 'pending') return isReady(model, t) ? 'ready' : 'waiting';
29
- return t.status;
30
- }
31
15
  function matches(model, t, f) {
32
- if (f === 'all') return true;
33
- if (f === 'running') return t.status === 'in_progress';
34
- if (f === 'blocked') return t.status === 'blocked';
35
- if (f === 'done') return t.status === 'completed';
36
- return isReady(model, t);
16
+ return f === 'all' || Studio.status.bucket(t) === f;
37
17
  }
38
18
 
39
19
  function toolbar(model) {
40
20
  var chips = FILTERS.map(function (f) {
41
21
  var on = view.filter === f[0];
42
22
  var n = model.tasks.filter(function (t) { return matches(model, t, f[0]); }).length;
23
+ if (!n && !on && f[0] !== 'all') return '';
43
24
  return '<button type="button" class="tf' + (on ? ' on' : '') + '" data-act="tasks-filter" data-filter="' +
44
25
  f[0] + '" aria-pressed="' + on + '">' + f[1] + ' <span class="num">' + n + '</span></button>';
45
26
  }).join('');
@@ -59,15 +40,17 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
59
40
 
60
41
  // Rows use data-tid, not data-task, so the nested Dispatch button gets its own click.
61
42
  function listRow(model, t, fmt) {
62
- var k = kindOf(model, t);
43
+ var bucket = Studio.status.bucket(t);
44
+ var wait = Studio.status.waitText(t);
63
45
  var id = ui.esc(t.id);
64
- var go = k === 'ready' ? '<button type="button" class="tdispatch" data-act="tasks-dispatch" data-tid="' +
46
+ var go = Studio.status.isReady(t) ? '<button type="button" class="tdispatch" data-act="tasks-dispatch" data-tid="' +
65
47
  id + '">Dispatch</button>' : '';
66
48
  return '<div class="trow" role="row" tabindex="0" data-act="tasks-open" data-tid="' + id + '">' +
67
49
  '<span class="c-id mono" role="cell">' + id + '</span>' +
68
50
  '<span class="c-title" role="cell" title="' + ui.esc(t.title) + '">' + ui.esc(t.title) + '</span>' +
69
- '<span class="c-agent" role="cell">' + ui.esc(t.agent || '') + '</span>' +
70
- '<span class="c-status st-' + DOT[k] + '" role="cell"><i class="dot"></i>' + LABEL[k] + go + '</span>' +
51
+ '<span class="c-agent" role="cell">' + ui.esc(Studio.agentInfo(t.agent).label) + '</span>' +
52
+ '<span class="c-status st-' + bucket + '" role="cell">' + Studio.status.word(bucket) +
53
+ (wait ? ' <span class="faint">' + ui.esc(wait) + '</span>' : '') + go + '</span>' +
71
54
  '<span class="c-time mono" role="cell">' + ui.esc(timeOf(t, fmt)) + '</span></div>';
72
55
  }
73
56
 
@@ -86,7 +69,7 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
86
69
  return '<section class="tgroup" role="table" aria-label="' + ui.esc(m.name) + '">' +
87
70
  '<button type="button" class="tg-head" data-act="tasks-group" data-group="' + ui.esc(m.id) +
88
71
  '" aria-expanded="' + open + '"><span class="chev">' + ui.icon('chev', 2) + '</span>' +
89
- '<span class="mono faint">' + ui.esc(m.id) + '</span><span class="t">' + ui.esc(m.name) + '</span>' +
72
+ '<span class="mono faint">' + ui.esc(m.id) + '</span><span class="t" title="' + ui.esc(m.name) + '">' + ui.esc(m.name) + '</span>' +
90
73
  '<span class="num faint">' + m.done + ' of ' + m.total + '</span></button>' + body + '</section>';
91
74
  }
92
75
 
@@ -124,13 +107,13 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
124
107
  var el = e.target;
125
108
  if (e.key !== 'Enter' || !el || !el.matches || !el.matches('.trow[data-tid]')) return;
126
109
  e.preventDefault();
127
- Studio.taskAction('logs', el.getAttribute('data-tid'));
110
+ Studio.openTask(el.getAttribute('data-tid'));
128
111
  });
129
112
  }
130
113
 
131
114
  function head(m) {
132
115
  var lead = m ? ui.esc(m.name) : 'The work of one milestone, by state.';
133
- return '<div class="panel-head"><div class="panel-title"><h1>Tasks</h1><p class="lead">' + lead + '</p></div>' +
116
+ return '<div class="panel-head"><div class="panel-title"><h1>Tasks</h1><p class="lead" title="' + lead + '">' + lead + '</p></div>' +
134
117
  '<div class="panel-actions">' + ui.milestoneSelect() + '</div></div>';
135
118
  }
136
119
 
@@ -138,7 +121,8 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
138
121
 
139
122
  function metaHtml(t, fmt) {
140
123
  if (t.status === 'completed') {
141
- return '<span>' + ui.esc(fmt.ago(t.endT)) + '</span>' + (t.durationMs ? '<span>took ' + ui.esc(fmt.dur(t.durationMs)) + '</span>' : '');
124
+ var took = t.durationMs ? '<span>took ' + ui.esc(fmt.dur(t.durationMs)) + '</span>' : '';
125
+ return '<span>' + ui.esc(fmt.ago(t.endT)) + '</span>' + took;
142
126
  }
143
127
  if (t.status === 'in_progress') {
144
128
  var live = t.startT ? '<span data-since="' + t.startT + '">' + fmt.dur(Studio.now() - t.startT) + '</span>' : '';
@@ -147,7 +131,7 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
147
131
  if (t.status === 'blocked') {
148
132
  return '<span class="bad">Paused after ' + (t.attempt || t.maxAttempts) + ' of ' + t.maxAttempts + ' attempts</span>';
149
133
  }
150
- if (t.waitingOn.length) return '<span>Waiting on ' + t.waitingOn.map(ui.esc).join(', ') + '</span>';
134
+ if (t.waitingOn.length && !t.stale) return '<span>Waits for ' + t.waitingOn.map(ui.esc).join(', ') + '</span>';
151
135
  return '<span>Ready to start</span>';
152
136
  }
153
137
 
@@ -157,8 +141,9 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
157
141
  '<div class="title">' + ui.esc(t.title) + '</div><div class="meta">' + metaHtml(t, fmt) + '</div></button>';
158
142
  }
159
143
 
160
- function column(label, status, list, shown, empty, extra, fmt) {
161
- var body = shown.length ? shown.map(function (t) { return card(t, fmt); }).join('') : '<div class="col-empty">' + empty + '</div>';
144
+ function column(label, status, list, shown, extra, fmt) {
145
+ var body = shown.length ? shown.map(function (t) { return card(t, fmt); }).join('')
146
+ : '<p class="col-empty">None</p>';
162
147
  return '<div class="col" data-col="' + status + '"><div class="col-head">' + ui.statusIcon(status) + label +
163
148
  '<span class="count">' + list.length + '</span></div>' + body + (extra || '') + '</div>';
164
149
  }
@@ -169,12 +154,12 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
169
154
  var shown = view.showAll ? done : done.slice(0, CAP);
170
155
  var more = done.length > CAP
171
156
  ? '<button type="button" class="more" data-act="tasks-more">' + (view.showAll ? 'Show fewer' : 'Show all ' + done.length) + '</button>' : '';
172
- var up = by('pending'), run = by('in_progress'), stuck = by('blocked');
173
- return '<div class="board">' +
174
- column('Up next', 'pending', up, up, 'Nothing queued', '', fmt) +
175
- column('Running', 'in_progress', run, run, 'No task is running', '', fmt) +
176
- column('Just finished', 'completed', done, shown, 'Nothing finished yet', more, fmt) +
177
- column('Stuck', 'blocked', stuck, stuck, 'Nothing is stuck', '', fmt) + '</div>';
157
+ var lanes = [['Up next', 'pending', by('pending'), null], ['Running', 'in_progress', by('in_progress'), null],
158
+ ['Done', 'completed', done, shown], ['Stuck', 'blocked', by('blocked'), null]];
159
+ var cols = lanes.map(function (l) {
160
+ return column(l[0], l[1], l[2], l[3] || l[2], l[1] === 'completed' ? more : '', fmt);
161
+ }).join('');
162
+ return '<div class="board">' + cols + '</div>';
178
163
  }
179
164
 
180
165
  function histRow(m, fmt) {
@@ -224,7 +209,7 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
224
209
  },
225
210
  'tasks-filter': function (el) { view.filter = el.getAttribute('data-filter'); Studio.repaint('tasks'); },
226
211
  'tasks-clear': function () { view.filter = 'all'; Studio.repaint('tasks'); },
227
- 'tasks-open': function (el) { Studio.taskAction('logs', el.getAttribute('data-tid')); },
212
+ 'tasks-open': function (el) { Studio.openTask(el.getAttribute('data-tid')); },
228
213
  'tasks-dispatch': function (el) { Studio.taskAction('dispatch', el.getAttribute('data-tid')); },
229
214
  'tasks-group': function (el, ctx) {
230
215
  var id = el.getAttribute('data-group');
@@ -3,7 +3,7 @@ export const tasksCss = String.raw `#panel-tasks .board { display: grid; grid-te
3
3
  #panel-tasks .col { background: var(--surface-2); border-radius: 6px; padding: 8px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
4
4
  #panel-tasks .col-head { display: flex; align-items: center; gap: 8px; padding: 4px 6px 2px; font-weight: 500; font-size: 13px; }
5
5
  #panel-tasks .col-head .count { margin-left: auto; background: var(--surface); border-radius: 999px; padding: 0 8px; font-size: 12px; color: var(--fg-2); }
6
- #panel-tasks .col-empty { font-size: 12.5px; color: var(--fg-3); padding: 14px 8px; text-align: center; border: 1px dashed var(--line-2); border-radius: 6px; }
6
+ #panel-tasks .col-empty { margin: 0; font-size: 12.5px; color: var(--fg-3); padding: 4px 2px; }
7
7
  #panel-tasks .tcard { display: flex; flex-direction: column; gap: 6px; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 10px; text-align: left; width: 100%; cursor: pointer; }
8
8
  #panel-tasks .tcard:hover { border-color: var(--line-2); }
9
9
  #panel-tasks .tcard.in_progress { border-color: color-mix(in srgb, var(--run) 45%, var(--line)); }
@@ -51,24 +51,24 @@ export const tasksCss = String.raw `#panel-tasks .board { display: grid; grid-te
51
51
  #panel-tasks .tg-head[aria-expanded="true"] .chev { transform: rotate(90deg); }
52
52
  #panel-tasks .trow {
53
53
  display: grid; grid-template-columns: 76px minmax(0, 1fr) 110px 150px 72px; gap: 12px;
54
- align-items: center; height: 36px; padding: 0 8px; border-bottom: 1px solid var(--line);
54
+ align-items: center; height: 36px; padding: 0 8px;
55
55
  font-size: 13px; cursor: pointer;
56
56
  }
57
57
  #panel-tasks .trow:hover, #panel-tasks .trow:focus-visible { background: var(--surface-2); }
58
58
  #panel-tasks .trow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
59
- #panel-tasks .trow.thead { cursor: default; color: var(--fg-3); font-size: 12px; background: none; }
59
+ #panel-tasks .trow.thead { border-bottom: 1px solid var(--line); cursor: default; color: var(--fg-3); font-size: 12px; background: none; }
60
60
  #panel-tasks .trow .mono { font-variant-numeric: tabular-nums; font-size: 12px; }
61
61
  #panel-tasks .c-id { color: var(--fg-3); }
62
62
  #panel-tasks .c-title, #panel-tasks .c-agent { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
63
63
  #panel-tasks .c-agent { color: var(--fg-2); }
64
64
  #panel-tasks .c-time { text-align: right; color: var(--fg-2); }
65
65
  #panel-tasks .c-status { display: flex; align-items: center; gap: 6px; }
66
- #panel-tasks .c-status .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
67
- #panel-tasks .st-ok { color: var(--ok); }
68
- #panel-tasks .st-run { color: var(--run); }
69
- #panel-tasks .st-bad { color: var(--bad); }
70
- #panel-tasks .st-fg { color: var(--fg); }
71
- #panel-tasks .st-faint { color: var(--fg-3); }
66
+ #panel-tasks .c-status { white-space: nowrap; overflow: hidden; }
67
+ #panel-tasks .st-done { color: var(--ok); }
68
+ #panel-tasks .st-running { color: var(--run); }
69
+ #panel-tasks .st-stuck { color: var(--bad); }
70
+ #panel-tasks .st-next { color: var(--fg); }
71
+ #panel-tasks .panel-title .lead { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
72
72
  #panel-tasks .tdispatch {
73
73
  display: none; margin-left: auto; border: 1px solid var(--line-2); background: var(--surface);
74
74
  color: var(--fg); border-radius: 6px; padding: 2px 8px; font-size: 12px; cursor: pointer;