@njeybe/nativ 2.6.0 → 2.8.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 (80) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +71 -1
  3. package/dist/commands/design-check.d.ts +4 -0
  4. package/dist/commands/design-check.js +76 -0
  5. package/dist/commands/init.d.ts +1 -0
  6. package/dist/commands/init.js +53 -1
  7. package/dist/commands/update.js +46 -0
  8. package/dist/core/design-rules.d.ts +23 -0
  9. package/dist/core/design-rules.js +115 -0
  10. package/dist/core/harness-integrity.js +26 -7
  11. package/dist/core/setup/pm-playbook.d.ts +1 -1
  12. package/dist/core/setup/pm-playbook.js +1 -1
  13. package/dist/index.js +11 -0
  14. package/dist/server/studio-ui/pages/benchmarks/script.js +9 -8
  15. package/dist/server/studio-ui/pages/benchmarks/styles.js +11 -6
  16. package/dist/server/studio-ui/pages/database/browser.js +2 -2
  17. package/dist/server/studio-ui/pages/database/connection.js +6 -5
  18. package/dist/server/studio-ui/pages/database/data-shell.js +12 -17
  19. package/dist/server/studio-ui/pages/database/drift.js +2 -2
  20. package/dist/server/studio-ui/pages/database/events.js +1 -2
  21. package/dist/server/studio-ui/pages/database/explorer.js +6 -6
  22. package/dist/server/studio-ui/pages/database/styles.js +5 -5
  23. package/dist/server/studio-ui/pages/flow/layout.js +1 -1
  24. package/dist/server/studio-ui/pages/flow/styles.js +4 -4
  25. package/dist/server/studio-ui/pages/flow/timeline-view.js +1 -1
  26. package/dist/server/studio-ui/pages/home/script.js +108 -92
  27. package/dist/server/studio-ui/pages/home/styles.js +29 -45
  28. package/dist/server/studio-ui/pages/tasks/script.js +146 -14
  29. package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
  30. package/dist/server/studio-ui/pages/team/script.js +69 -53
  31. package/dist/server/studio-ui/pages/team/styles.js +20 -20
  32. package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
  33. package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
  34. package/dist/server/studio-ui/shell/brand.d.ts +4 -0
  35. package/dist/server/studio-ui/shell/brand.js +16 -0
  36. package/dist/server/studio-ui/shell/console.js +71 -6
  37. package/dist/server/studio-ui/shell/core.js +8 -8
  38. package/dist/server/studio-ui/shell/detail-view.js +4 -4
  39. package/dist/server/studio-ui/shell/dispatch.js +23 -4
  40. package/dist/server/studio-ui/shell/events-tools.js +16 -1
  41. package/dist/server/studio-ui/shell/format.js +36 -8
  42. package/dist/server/studio-ui/shell/icons.js +23 -15
  43. package/dist/server/studio-ui/shell/markup-head.js +11 -6
  44. package/dist/server/studio-ui/shell/markup-tools.js +29 -15
  45. package/dist/server/studio-ui/shell/model.js +8 -2
  46. package/dist/server/studio-ui/shell/paint.js +4 -3
  47. package/dist/server/studio-ui/shell/pipeline-state.js +2 -11
  48. package/dist/server/studio-ui/shell/proposals.js +6 -2
  49. package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
  50. package/dist/server/studio-ui/shell/pulse.js +68 -0
  51. package/dist/server/studio-ui/shell/router.js +25 -0
  52. package/dist/server/studio-ui/shell/sidebar-view.js +7 -5
  53. package/dist/server/studio-ui/shell/toasts-format.js +4 -4
  54. package/dist/server/studio-ui/shell/triage.js +1 -1
  55. package/dist/server/studio-ui/styles/console.js +36 -13
  56. package/dist/server/studio-ui/styles/dialogs.js +4 -4
  57. package/dist/server/studio-ui/styles/responsive.js +8 -0
  58. package/dist/server/studio-ui/styles/shell.js +23 -6
  59. package/dist/server/studio-ui/styles/strategist.js +10 -10
  60. package/dist/server/studio-ui/styles/tokens-base.js +23 -23
  61. package/dist/server/studio-ui.js +2 -0
  62. package/package.json +1 -1
  63. package/plugin/.claude-plugin/plugin.json +1 -1
  64. package/plugin/agents/architect.md +11 -12
  65. package/plugin/agents/verifier.md +4 -1
  66. package/plugin/skills/nativ/SKILL.md +7 -7
  67. package/templates/AGENTS.md +7 -7
  68. package/templates/GEMINI.md +4 -3
  69. package/templates/claude-agents/architect.md +11 -12
  70. package/templates/claude-agents/verifier.md +4 -1
  71. package/templates/design/taste/LICENSE +21 -0
  72. package/templates/design/taste/SOURCE.md +28 -0
  73. package/templates/design/taste/v1/DIGEST.md +57 -0
  74. package/templates/design/taste/v1/SKILL.md +226 -0
  75. package/templates/design/taste/v2/DIGEST.md +62 -0
  76. package/templates/design/taste/v2/SKILL.md +1206 -0
  77. package/templates/dot-ai/subagents/architect.md +8 -2
  78. package/templates/dot-ai/subagents/flutter-developer.md +2 -11
  79. package/templates/dot-ai/subagents/frontend.md +47 -44
  80. package/templates/dot-ai/ui_specs.md +15 -36
@@ -1,11 +1,11 @@
1
- // Board and history styles, scoped under the Tasks panel.
1
+ // List, Board and history styles, scoped under the Tasks panel (Ink: 6px radius, 1px dividers).
2
2
  export const tasksCss = String.raw `#panel-tasks .board { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; align-items: start; }
3
- #panel-tasks .col { background: var(--surface-2); border-radius: 12px; padding: 8px; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
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: 8px; }
7
- #panel-tasks .tcard { display: flex; flex-direction: column; gap: 6px; background: var(--surface); border: 1px solid var(--line); border-radius: 9px; padding: 10px; text-align: left; width: 100%; cursor: pointer; }
8
- #panel-tasks .tcard:hover { border-color: var(--line-2); box-shadow: var(--shadow); }
6
+ #panel-tasks .col-empty { margin: 0; font-size: 12.5px; color: var(--fg-3); padding: 4px 2px; }
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
+ #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)); }
10
10
  #panel-tasks .tcard.blocked { border-color: color-mix(in srgb, var(--bad) 45%, var(--line)); }
11
11
  #panel-tasks .tcard .l1 { display: flex; align-items: center; gap: 6px; }
@@ -17,7 +17,7 @@ export const tasksCss = String.raw `#panel-tasks .board { display: grid; grid-te
17
17
  #panel-tasks .more { border: 0; background: none; color: var(--accent); font-size: 12.5px; padding: 6px; cursor: pointer; text-align: center; }
18
18
  #panel-tasks .more:hover { text-decoration: underline; }
19
19
  #panel-tasks .section-title { margin: 22px 0 8px; font-size: 15px; font-weight: 600; }
20
- #panel-tasks .hist { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
20
+ #panel-tasks .hist { background: var(--surface); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
21
21
  #panel-tasks .hist-row { display: grid; grid-template-columns: 16px 34px minmax(0, 1fr) auto 70px 90px 60px; gap: 12px; align-items: center; padding: 9px 16px; border: 0; border-top: 1px solid var(--line); background: none; text-align: left; width: 100%; cursor: pointer; }
22
22
  #panel-tasks .hist > div:first-child .hist-row { border-top: 0; }
23
23
  #panel-tasks .hist-row:hover { background: var(--surface-2); }
@@ -28,6 +28,63 @@ export const tasksCss = String.raw `#panel-tasks .board { display: grid; grid-te
28
28
  #panel-tasks .hist-row .small { font-size: 12px; }
29
29
  #panel-tasks .hist-tasks { background: var(--surface-2); }
30
30
  #panel-tasks .hist-tasks .row-btn { padding-left: 46px; }
31
+ #panel-tasks .tbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
32
+ #panel-tasks .tfilters, #panel-tasks .tseg { display: flex; gap: 4px; }
33
+ #panel-tasks .tseg { margin-left: auto; border: 1px solid var(--line); border-radius: 6px; padding: 2px; }
34
+ #panel-tasks .tf, #panel-tasks .seg {
35
+ border: 1px solid transparent; background: none; color: var(--fg-2); border-radius: 6px;
36
+ padding: 4px 10px; font-size: 12.5px; cursor: pointer;
37
+ }
38
+ #panel-tasks .tf { border-color: var(--line); }
39
+ #panel-tasks .tf .num { font-variant-numeric: tabular-nums; color: var(--fg-3); }
40
+ #panel-tasks .tf.on, #panel-tasks .seg.on { background: var(--surface-2); color: var(--fg); border-color: var(--line-2); }
41
+ #panel-tasks .tlist { border-top: 1px solid var(--line); }
42
+ #panel-tasks .tg-head {
43
+ display: flex; align-items: center; gap: 10px; width: 100%; height: 36px; padding: 0 8px; border: 0;
44
+ border-bottom: 1px solid var(--line); background: none; color: var(--fg); text-align: left;
45
+ cursor: pointer; font-weight: 600;
46
+ }
47
+ #panel-tasks .tg-head .t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
48
+ #panel-tasks .tg-head .num { font-variant-numeric: tabular-nums; font-weight: 400; font-size: 12px; }
49
+ #panel-tasks .tg-head .chev { display: inline-grid; transition: transform .15s; }
50
+ #panel-tasks .tg-head .chev svg { width: 14px; height: 14px; }
51
+ #panel-tasks .tg-head[aria-expanded="true"] .chev { transform: rotate(90deg); }
52
+ #panel-tasks .trow {
53
+ display: grid; grid-template-columns: 76px minmax(0, 1fr) 110px 150px 72px; gap: 12px;
54
+ align-items: center; height: 36px; padding: 0 8px;
55
+ font-size: 13px; cursor: pointer;
56
+ }
57
+ #panel-tasks .trow:hover, #panel-tasks .trow:focus-visible { background: var(--surface-2); }
58
+ #panel-tasks .trow:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
59
+ #panel-tasks .trow.thead { border-bottom: 1px solid var(--line); cursor: default; color: var(--fg-3); font-size: 12px; background: none; }
60
+ #panel-tasks .trow .mono { font-variant-numeric: tabular-nums; font-size: 12px; }
61
+ #panel-tasks .c-id { color: var(--fg-3); }
62
+ #panel-tasks .c-title, #panel-tasks .c-agent { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
63
+ #panel-tasks .c-agent { color: var(--fg-2); }
64
+ #panel-tasks .c-time { text-align: right; color: var(--fg-2); }
65
+ #panel-tasks .c-status { display: flex; align-items: center; gap: 6px; }
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
+ #panel-tasks .tdispatch {
73
+ display: none; margin-left: auto; border: 1px solid var(--line-2); background: var(--surface);
74
+ color: var(--fg); border-radius: 6px; padding: 2px 8px; font-size: 12px; cursor: pointer;
75
+ }
76
+ #panel-tasks .trow:hover .tdispatch, #panel-tasks .trow:focus-within .tdispatch { display: inline-block; }
77
+ #panel-tasks .tskel span { height: 10px; border-radius: 6px; background: var(--surface-2); }
78
+ #panel-tasks .tempty { padding: 36px 16px; text-align: center; color: var(--fg-2); }
79
+ #panel-tasks .tempty .btn { margin-top: 8px; }
80
+ @media (max-width: 1024px) {
81
+ #panel-tasks .trow { grid-template-columns: 76px minmax(0, 1fr) 150px 72px; }
82
+ #panel-tasks .c-agent { display: none; }
83
+ }
84
+ @media (max-width: 768px) {
85
+ #panel-tasks .trow { grid-template-columns: 76px minmax(0, 1fr) 120px; }
86
+ #panel-tasks .c-time { display: none; }
87
+ }
31
88
  @media (max-width: 1100px) { #panel-tasks .board { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
32
89
  @media (max-width: 640px) {
33
90
  #panel-tasks .board { grid-template-columns: minmax(0, 1fr); }
@@ -1,111 +1,127 @@
1
- // Team page client script: one pure render, one table row per agent.
1
+ // Team page client script: one pure render, one roster row per agent.
2
2
  export const teamScript = String.raw ` var esc = Studio.ui.esc;
3
- var DASH = '–';
3
+ var DASH = '-';
4
4
 
5
5
  function head() {
6
- return '<div class="page-head"><div><h1>Team</h1><p>Each agent in plain terms: what it did, how long it took, what it cost.</p></div></div>';
7
- }
8
-
9
- function sum(list) {
10
- return list.reduce(function (total, n) { return total + n; }, 0);
6
+ return '<div class="page-head"><div><h1>Team</h1><p>Who is working on what, and how much each agent has finished.</p></div></div>';
11
7
  }
12
8
 
13
9
  function statsFor(key, tasks) {
14
10
  var done = tasks.filter(function (t) { return t.status === 'completed'; });
15
11
  var timed = done.filter(function (t) { return t.durationMs; });
16
- var work = sum(timed.map(function (t) { return t.durationMs; }));
17
12
  var costs = tasks.filter(function (t) { return t.costUsd != null; });
18
13
  var stamps = tasks.map(function (t) { return t.status === 'in_progress' ? (t.startT || 0) : (t.endT || 0); });
19
14
  return {
20
15
  agent: key || null,
16
+ tasks: tasks,
21
17
  total: tasks.length,
22
18
  done: done.length,
23
- work: timed.length ? work : null,
24
- avg: timed.length ? work / timed.length : null,
25
- cost: costs.length ? sum(costs.map(function (t) { return t.costUsd; })) : null,
19
+ work: timed.length ? timed.reduce(function (n, t) { return n + t.durationMs; }, 0) : null,
20
+ cost: costs.length ? costs.reduce(function (n, t) { return n + t.costUsd; }, 0) : null,
21
+ blocked: tasks.filter(function (t) { return t.status === 'blocked'; }).length,
26
22
  last: Math.max.apply(null, [0].concat(stamps)) || null,
27
- running: tasks.filter(function (t) { return t.status === 'in_progress'; })[0] || null,
28
- stuck: tasks.filter(function (t) { return t.status === 'blocked'; })[0] || null
23
+ running: tasks.filter(function (t) { return t.status === 'in_progress'; })[0] || null
29
24
  };
30
25
  }
31
26
 
32
27
  function buildRows(model) {
33
28
  var groups = {};
34
29
  var order = [];
30
+ var ids = {};
35
31
  model.tasks.forEach(function (t) {
36
- var key = t.agent || '';
37
- if (!groups[key]) { groups[key] = []; order.push(key); }
32
+ var key = t.agent ? Studio.agentInfo(t.agent).label : '';
33
+ if (!groups[key]) { groups[key] = []; ids[key] = []; order.push(key); }
38
34
  groups[key].push(t);
35
+ if (ids[key].indexOf(t.agent) < 0) ids[key].push(t.agent);
39
36
  });
40
- return order.map(function (k) { return statsFor(k, groups[k]); })
37
+ return order.map(function (k) {
38
+ var plain = ids[k].filter(function (i) { return !/-agent$/.test(i || ''); });
39
+ var id = plain.length ? plain[0] : String(ids[k][0] || '').replace(/-agent$/, '');
40
+ var r = statsFor(k ? id : '', groups[k]);
41
+ r.label = k || 'Unassigned';
42
+ if (ids[k].length === 1) r.agent = ids[k][0] || null;
43
+ return r;
44
+ })
41
45
  .sort(function (a, b) { return b.total - a.total; });
42
46
  }
43
47
 
44
- function chip(task, label) {
45
- return '<button type="button" class="chip" data-task="' + esc(task.id) + '">' +
46
- Studio.ui.statusIcon(task.status) + esc(label) + '</button>';
48
+ function currentTask(r) {
49
+ var t = r.running;
50
+ if (!t) return waiting(r);
51
+ return taskButton(t);
47
52
  }
48
53
 
49
- function rightNow(r) {
50
- if (r.running) return chip(r.running, r.running.id);
51
- if (r.stuck) return chip(r.stuck, 'Stuck on ' + r.stuck.id);
52
- return '<span class="faint">Idle</span>';
54
+ function waiting(r) {
55
+ var stuck = r.tasks.filter(function (t) { return Studio.status.bucket(t) === 'stuck'; })[0];
56
+ if (stuck) return '<span class="idle">Stuck</span> ' + taskButton(stuck);
57
+ var next = r.tasks.some(function (t) { return Studio.status.bucket(t) === 'next'; });
58
+ return '<span class="idle">' + (next ? 'Up next' : 'Done') + '</span>';
53
59
  }
54
60
 
55
- function shareBar(r, max) {
56
- var info = Studio.agentInfo(r.agent);
57
- var pct = max ? Math.round((r.total / max) * 100) : 0;
58
- return '<div class="bar"><i style="width:' + pct + '%;background:var(' + info.token + ')"></i></div>';
61
+ function taskButton(t) {
62
+ return '<button type="button" class="cur" data-act="team-open" data-tid="' + esc(t.id) +
63
+ '" title="' + esc(t.title || t.id) + '"><span class="mono">' + esc(t.id) + '</span>' +
64
+ '<span class="ttl">' + esc(t.title || '') + '</span></button>';
65
+ }
66
+
67
+ function rate(r) {
68
+ var fin = r.done + r.blocked;
69
+ return fin ? Math.round((r.done / fin) * 100) + '%' : DASH;
59
70
  }
60
71
 
61
72
  function lastActive(r) {
62
73
  return r.last ? Studio.ui.when(r.last) : DASH;
63
74
  }
64
75
 
65
- function row(r, max) {
66
- return '<tr data-agent="' + esc(r.agent || 'unassigned') + '"><td>' + Studio.ui.agentChip(r.agent) + '</td>' +
67
- '<td>' + rightNow(r) + '</td>' +
68
- '<td class="r">' + r.done + '<span class="faint"> / ' + r.total + '</span></td>' +
69
- '<td class="share">' + shareBar(r, max) + '</td>' +
70
- '<td class="r">' + (r.avg == null ? DASH : esc(Studio.fmt.dur(r.avg))) + '</td>' +
71
- '<td class="r">' + (r.work == null ? DASH : esc(Studio.fmt.dur(r.work))) + '</td>' +
72
- '<td class="r">' + (r.cost == null ? DASH : esc(Studio.fmt.money(r.cost))) + '</td>' +
73
- '<td class="r faint">' + lastActive(r) + '</td></tr>';
76
+ function row(r) {
77
+ return '<tr data-agent="' + esc(r.agent || 'unassigned') + '"><td class="who">' + esc(r.label) + '</td>' +
78
+ '<td>' + currentTask(r) + '</td>' +
79
+ '<td class="mono r">' + r.done + '</td>' +
80
+ '<td class="mono r hide-sm">' + (r.work == null ? DASH : esc(Studio.fmt.dur(r.work))) + '</td>' +
81
+ '<td class="mono r hide-sm">' + (r.cost == null ? DASH : esc(Studio.fmt.money(r.cost))) + '</td>' +
82
+ '<td class="mono r hide-sm">' + rate(r) + '</td>' +
83
+ '<td class="mono r hide-sm">' + lastActive(r) + '</td></tr>';
74
84
  }
75
85
 
76
86
  function table(rows) {
77
- var max = Math.max.apply(null, rows.map(function (r) { return r.total; }));
78
- var cols = ['Agent', 'Right now', 'Tasks done', 'Share of work', 'Avg. time', 'Total time', 'Est. cost', 'Last active'];
79
- var th = cols.map(function (c, i) {
80
- var cls = i === 3 ? ' class="share"' : (i > 1 ? ' class="r"' : '');
81
- return '<th' + cls + '>' + c + '</th>';
87
+ var cols = [['Agent', ''], ['Current task', ''], ['Tasks done', 'r'],
88
+ ['Time', 'r hide-sm'], ['Cost', 'r hide-sm'], ['Success rate', 'r hide-sm'], ['Last active', 'r hide-sm']];
89
+ var th = cols.map(function (c) {
90
+ var tip = c[0] === 'Success rate' ? ' title="Completed / (completed + blocked)"' : '';
91
+ return '<th' + tip + (c[1] ? ' class="' + c[1] + '"' : '') + '>' + c[0] + '</th>';
82
92
  }).join('');
83
- return '<section class="panel table-wrap"><table><thead><tr>' + th + '</tr></thead><tbody>' +
84
- rows.map(function (r) { return row(r, max); }).join('') + '</tbody></table></section>';
93
+ return '<div class="roster"><table><thead><tr>' + th + '</tr></thead><tbody>' +
94
+ rows.map(row).join('') + '</tbody></table></div>' + defs();
85
95
  }
86
96
 
87
97
  function defs() {
88
- return '<section class="panel"><div class="panel-head"><h2>What these columns mean</h2></div><div class="panel-body defs">' +
89
- '<div><b>Tasks done</b>: finished and checked, out of all tasks given to this agent.</div>' +
90
- '<div><b>Avg. time</b>: from start to a passing check, for tasks that recorded timing.</div>' +
91
- '<div><b>Est. cost</b>: estimated from token counts. The real bill is on Home under Spend.</div>' +
92
- '<div><b>Right now</b>: the task this agent is working on, or the one it is stuck on.</div></div></section>';
98
+ var items = [['Tasks done', 'finished and checked by the gatekeeper.'],
99
+ ['Time', 'total time on completed tasks that recorded timing.'],
100
+ ['Cost', 'estimated from token counts; the real bill is on Home.'],
101
+ ['Current task', 'the task this agent is working on right now.']];
102
+ return '<details class="defs"><summary>What these columns mean</summary>' + items.map(function (i) {
103
+ return '<div><b>' + i[0] + '</b><span>' + i[1] + '</span></div>';
104
+ }).join('') + '</details>';
93
105
  }
94
106
 
95
107
  function skeleton() {
96
- var bar = function (w) { return '<div class="skel" style="width:' + w + '"></div>'; };
97
- var block = '<div class="skel-block">' + bar('100%') + bar('92%') + bar('74%') + '</div>';
98
- return head() + block + block;
108
+ var line = function (w) {
109
+ return '<tr><td colspan="6"><div class="skel" style="width:' + w + '"></div></td></tr>';
110
+ };
111
+ var rows = ['92%', '78%', '86%', '70%'].map(line).join('');
112
+ return head() + '<div class="roster"><table><thead>' + line('100%') + '</thead><tbody>' +
113
+ rows + '</tbody></table></div>';
99
114
  }
100
115
 
101
116
  function render(ctx) {
102
117
  var rows = buildRows(ctx.model);
103
- if (!rows.length) return head() + '<section class="panel">' + ctx.ui.empty('No tasks yet', 'Agents show up here once the plan has tasks.') + '</section>';
104
- return head() + table(rows) + defs();
118
+ if (!rows.length) return head() + '<p class="empty">No agents have worked on this project yet.</p>';
119
+ return head() + table(rows);
105
120
  }
106
121
 
107
122
  Studio.registerPage('team', {
108
123
  deps: ['tasks', 'telemetry', 'runs'],
124
+ actions: { 'team-open': function (el) { Studio.openTask(el.getAttribute('data-tid')); } },
109
125
  render: render,
110
126
  skeleton: skeleton
111
127
  });
@@ -1,22 +1,22 @@
1
- // Team page styles, all scoped under #panel-team.
2
- export const teamCss = String.raw `#panel-team .page-head { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
3
- #panel-team .page-head h1 { font-size: 22px; font-weight: 600; margin: 0; letter-spacing: -.01em; text-wrap: balance; }
1
+ // Team page styles, all scoped under #panel-team. Ink look: no cards, 1px dividers.
2
+ export const teamCss = String.raw `#panel-team .page-head h1 { font-size: 22px; font-weight: 600; margin: 0; letter-spacing: -.01em; text-wrap: balance; }
4
3
  #panel-team .page-head p { margin: 4px 0 0; color: var(--fg-2); }
5
- #panel-team .panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; min-width: 0; margin-top: 16px; }
6
- #panel-team .panel-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
7
- #panel-team .panel-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
8
- #panel-team .panel-body { padding: 14px 16px; }
9
- #panel-team .table-wrap { overflow-x: auto; }
10
- #panel-team table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
11
- #panel-team th { text-align: left; font-weight: 500; font-size: 12px; color: var(--fg-3); padding: 10px 16px; border-bottom: 1px solid var(--line); white-space: nowrap; }
12
- #panel-team td { padding: 11px 16px; border-bottom: 1px solid var(--line); white-space: nowrap; }
13
- #panel-team tr:last-child td { border-bottom: 0; }
14
- #panel-team td.r, #panel-team th.r { text-align: right; }
15
- #panel-team .share { width: 110px; }
16
- #panel-team .bar { height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
17
- #panel-team .bar i { display: block; height: 100%; border-radius: 3px; }
18
- #panel-team .chip { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px 2px 6px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); font: 12px var(--mono); cursor: pointer; }
19
- #panel-team .chip:hover { background: var(--surface-2); }
20
- #panel-team .defs { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px 24px; font-size: 13px; color: var(--fg-2); }
21
- #panel-team .defs b { color: var(--fg); font-weight: 500; }
4
+ #panel-team .roster { margin-top: 16px; overflow-x: auto; }
5
+ #panel-team table { width: 100%; border-collapse: collapse; }
6
+ #panel-team th { text-align: left; font-weight: 500; font-size: 12px; color: var(--fg-3); height: 36px; padding: 0 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
7
+ #panel-team td { height: 36px; padding: 0 12px; border-bottom: 1px solid var(--line); white-space: nowrap; }
8
+ #panel-team .r { text-align: right; }
9
+ #panel-team .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
10
+ #panel-team .who { font-weight: 500; }
11
+ #panel-team .idle { color: var(--fg-3); }
12
+ #panel-team .cur { display: inline-flex; gap: 8px; max-width: 360px; padding: 0; border: 0; background: none; color: var(--fg); font: inherit; cursor: pointer; }
13
+ #panel-team .cur:hover .ttl { text-decoration: underline; }
14
+ #panel-team .ttl { overflow: hidden; text-overflow: ellipsis; color: var(--fg-2); }
15
+ #panel-team .empty { margin: 24px 0; color: var(--fg-3); }
16
+ #panel-team .defs { margin-top: 24px; }
17
+ #panel-team .defs summary { cursor: pointer; color: var(--fg-2); height: 36px; line-height: 36px; }
18
+ #panel-team .defs div { display: flex; gap: 16px; height: 36px; align-items: center; border-bottom: 1px solid var(--line); }
19
+ #panel-team .defs b { width: 120px; font-weight: 500; }
20
+ #panel-team .defs span { color: var(--fg-2); }
21
+ @media (max-width: 1023px) { #panel-team .hide-sm { display: none; } }
22
22
  `;
@@ -11,50 +11,62 @@ export const worktreesScript = String.raw ` // ─── View 3: Agent worktree
11
11
  return task ? task.status : null;
12
12
  }
13
13
 
14
- function worktreeBadge(w, status) {
15
- if (!w.isAgentWorktree) return '<span class="badge badge-neutral">Main workspace</span>';
16
- if (status === 'completed') return '<span class="badge badge-success">Tests Passed (Ready to merge)</span>';
17
- if (status === 'in_progress') return '<span class="badge badge-active">Agent working</span>';
18
- if (status === 'blocked') return '<span class="badge badge-danger">Blocked</span>';
19
- if (status === 'pending') return '<span class="badge badge-neutral">Pending</span>';
20
- return '<span class="badge badge-neutral" title="No matching task in the master plan">Untracked</span>';
14
+ function wtStatus(w, status) {
15
+ if (!w.isAgentWorktree) return ['neutral', 'main'];
16
+ if (status === 'completed') return ['ok', 'ready to merge'];
17
+ if (status === 'in_progress') return ['run', 'agent working'];
18
+ if (status === 'blocked') return ['err', 'blocked'];
19
+ if (status === 'pending') return ['neutral', 'open'];
20
+ return ['neutral', 'stale'];
21
+ }
22
+
23
+ function wtChanges(w) {
24
+ var n = w.changedFiles != null ? w.changedFiles : w.files;
25
+ return n != null ? esc(String(n)) + ' files' : '<span class="muted">-</span>';
26
+ }
27
+
28
+ function wtActions(w, status) {
29
+ var id = esc(w.taskId);
30
+ var canMerge = w.mergeEligible != null ? !!w.mergeEligible : status === 'completed';
31
+ var why = w.mergeBlockedReason || 'Available once the task is completed';
32
+ var busy = pipe.busy['wt:' + w.taskId];
33
+ if (busy) {
34
+ return '<button class="btn small" type="button" disabled><span class="spinner" aria-hidden="true"></span>' +
35
+ (busy === 'merge' ? 'Merging...' : 'Deleting...') + '</button>';
36
+ }
37
+ return '<button class="btn small" type="button" data-act="wt-diff" data-key="' + id +
38
+ '" aria-label="Inspect diff for ' + id + '">View changes</button>' +
39
+ '<button class="btn small primary" type="button" data-act="wt-merge" data-key="' + id +
40
+ '" aria-label="Merge ' + id + ' to main"' + (canMerge ? '' : ' disabled title="' + esc(why) + '"') +
41
+ '>Merge</button>' +
42
+ '<button class="btn small danger" type="button" data-act="wt-remove" data-key="' + id +
43
+ '" aria-label="Delete workspace for ' + id + '">Remove</button>';
21
44
  }
22
45
 
23
46
  function worktreeRow(w) {
24
47
  var status = worktreeTaskStatus(w);
25
48
  var task = w.taskId ? taskById(w.taskId) : null;
26
- // The server decides merge eligibility (and why not); fall back to the task status from the plan.
27
- var canMerge = w.mergeEligible != null ? !!w.mergeEligible : status === 'completed';
28
- var blockedWhy = w.mergeBlockedReason || 'Available once the task is completed';
29
- var busy = w.taskId && pipe.busy['wt:' + w.taskId];
30
- var actions = '<span class="muted">–</span>';
31
- if (w.isAgentWorktree && w.taskId) {
32
- actions = busy
33
- ? '<button class="btn small" type="button" disabled><span class="spinner" aria-hidden="true"></span>' + (busy === 'merge' ? 'Merging…' : 'Deleting…') + '</button>'
34
- : '<button class="btn small" type="button" data-act="wt-diff" data-key="' + esc(w.taskId) + '" aria-label="' + esc('Inspect diff for ' + w.taskId) + '">Inspect Diff</button>' +
35
- '<button class="btn small primary" type="button" data-act="wt-merge" data-key="' + esc(w.taskId) + '"' + (canMerge ? '' : ' disabled title="' + esc(blockedWhy) + '"') + '>Merge to Main</button>' +
36
- '<button class="btn small danger" type="button" data-act="wt-remove" data-key="' + esc(w.taskId) + '">Delete Workspace</button>';
37
- }
38
- return '<tr><td data-label="Branch"><code>' + esc(w.branch) + '</code></td>' +
39
- '<td data-label="Task ID">' + (w.taskId ? '<div><code>' + esc(w.taskId) + '</code>' + (task ? '<div class="cell-sub" title="' + esc(task.title) + '">' + esc(task.title) + '</div>' : '') + '</div>' : '<span class="muted">–</span>') + '</td>' +
40
- '<td data-label="Workspace Folder"><code class="path" title="' + esc(w.path) + '">' + esc(shortPath(w.path)) + '</code></td>' +
41
- '<td data-label="Latest Commit"><code>' + esc(String(w.head || '').slice(0, 7) || '–') + '</code></td>' +
42
- '<td data-label="Status">' + worktreeBadge(w, status) + '</td>' +
43
- '<td data-label="Actions"><div class="row-actions">' + actions + '</div></td></tr>';
49
+ var st = wtStatus(w, status);
50
+ var actions = w.isAgentWorktree && w.taskId ? wtActions(w, status) : '<span class="muted">-</span>';
51
+ var title = task ? task.title : '';
52
+ return '<tr class="wt-row"><td class="wt-task">' + (w.taskId ? '<code>' + esc(w.taskId) + '</code>' : '') +
53
+ '<span class="wt-title" title="' + esc(title) + '">' + esc(title) + '</span></td>' +
54
+ '<td><code>' + esc(w.branch) + '</code></td>' +
55
+ '<td><span class="wt-dot" data-tone="' + st[0] + '"></span>' + esc(st[1]) + '</td>' +
56
+ '<td class="wt-num">' + wtChanges(w) + '</td>' +
57
+ '<td><div class="row-actions">' + actions + '</div></td></tr>';
44
58
  }
45
59
 
46
60
  function renderWorktrees() {
47
- var head = panelHead('Agent Worktrees', 'Isolated git workspaces for tasks. Changes are tested automatically before merging into your main branch.');
48
- if (!pipe.loaded.worktrees) return head + '<div class="card section" aria-hidden="true"><div class="skeleton"></div><div class="skeleton"></div><div class="skeleton short"></div></div>';
61
+ var head = panelHead('Worktrees', 'Isolated git workspaces for tasks.');
62
+ if (!pipe.loaded.worktrees) return head + '<div class="section" aria-hidden="true"><div class="skeleton"></div><div class="skeleton short"></div></div>';
49
63
  if (pipe.errors.worktrees && !pipe.worktrees.length) return head + errorCard('worktrees', pipe.errors.worktrees, 'worktrees');
50
- var rows = pipe.worktrees.slice().sort(function (a, b) { return (a.isAgentWorktree ? 1 : 0) - (b.isAgentWorktree ? 1 : 0); });
51
- if (!rows.some(function (w) { return w.isAgentWorktree; })) {
52
- return head + staleNote('worktrees') + '<div class="card state"><div class="state-icon">' + ICON.branch + '</div><h2>No agent worktrees</h2>' +
53
- '<p>Run <code>nativ worktree create &lt;taskId&gt;</code> to give an agent its own isolated workspace folder for an independent task.</p></div>';
54
- }
55
- return head + staleNote('worktrees') + '<div class="card wt-card"><table class="grid"><thead><tr>' +
56
- '<th scope="col">Branch Name</th><th scope="col">Task ID</th><th scope="col">Workspace Folder</th><th scope="col">Latest Commit</th><th scope="col">Status</th><th scope="col">Actions</th>' +
57
- '</tr></thead><tbody>' + rows.map(worktreeRow).join('') + '</tbody></table></div>';
64
+ var rows = pipe.worktrees.filter(function (w) { return w.isAgentWorktree; });
65
+ if (!rows.length) return head + staleNote('worktrees') + '<div class="state"><p>No worktrees open.</p></div>';
66
+ return head + staleNote('worktrees') + '<table class="grid wt-table"><thead><tr>' +
67
+ '<th scope="col">Task</th><th scope="col">Branch</th><th scope="col">Status</th>' +
68
+ '<th scope="col">Changes</th><th scope="col">Actions</th>' +
69
+ '</tr></thead><tbody>' + rows.map(worktreeRow).join('') + '</tbody></table>';
58
70
  }
59
71
 
60
72
  function confirmWorktree(action, taskId) {
@@ -1,15 +1,17 @@
1
1
  // Worktrees view styles.
2
- export const worktreesCss = String.raw `/* Agent worktrees */
3
- .wt-card { overflow: hidden; }
4
- table.grid { width: 100%; border-collapse: collapse; font-size: 13.5px; }
5
- table.grid th { padding: 10px 16px; text-align: left; font-size: 12.5px; font-weight: 600; color: var(--muted); background: var(--bg); border-bottom: 1px solid var(--border); white-space: nowrap; }
2
+ export const worktreesCss = String.raw `/* Worktrees table */
3
+ table.grid { width: 100%; border-collapse: collapse; font-size: 13px; }
4
+ table.grid th { height: 32px; padding: 0 12px; text-align: left; font-size: 12px; font-weight: 500; color: var(--muted); border-bottom: 1px solid var(--border); white-space: nowrap; }
6
5
  table.grid th:last-child { text-align: right; }
7
- table.grid td { padding: 12px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
8
- table.grid tbody tr:last-child td { border-bottom: none; }
9
- table.grid tbody tr:hover td { background: var(--bg); }
10
- table.grid code { font-size: 12.5px; }
11
- .cell-sub { max-width: 280px; margin-top: 2px; font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
12
- .path { display: inline-block; max-width: 260px; vertical-align: bottom; font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6
+ table.grid td { height: 36px; padding: 0 12px; border-bottom: 1px solid var(--border); vertical-align: middle; }
7
+ table.grid code { font-family: var(--mono); font-size: 12px; }
8
+ .wt-task { display: flex; align-items: center; gap: 8px; height: 36px; max-width: 320px; }
9
+ .wt-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
10
+ .wt-num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
11
+ .wt-dot { display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--muted); }
12
+ .wt-dot[data-tone="ok"] { background: var(--ok); }
13
+ .wt-dot[data-tone="err"] { background: var(--danger); }
14
+ .wt-dot[data-tone="run"] { background: var(--warn); }
13
15
  .row-actions { display: flex; justify-content: flex-end; gap: 6px; }
14
16
 
15
17
  `;
@@ -0,0 +1,4 @@
1
+ export declare const WORDMARK_PATH: string;
2
+ export declare function wordmark(opts?: {
3
+ rail?: boolean;
4
+ }): string;
@@ -0,0 +1,16 @@
1
+ // The nativ wordmark: path from assets/brand/nativ-dark.svg, filled with currentColor.
2
+ export const WORDMARK_PATH = 'M88.9141 114.148H69.6172V69.6172C69.6172 67.0938 69.1719 64.8424 68.2812 62.8633C67.4401 60.8346 66.2773 59.1029 64.793 57.668C63.3086 56.2331 61.5521 55.1445 59.5234 54.4023C57.5443 53.6107 55.3919 53.2148 53.0664 53.2148H19.2969V114.148H0V43.4922C0 42.1562 0.247396 40.9193 0.742188 39.7812C1.23698 38.5938 1.92969 37.5794 2.82031 36.7383C3.71094 35.8477 4.75 35.1549 5.9375 34.6602C7.125 34.1654 8.38672 33.918 9.72266 33.918H53.2148C55.6393 33.918 58.1875 34.1901 60.8594 34.7344C63.5807 35.2786 66.2526 36.1693 68.875 37.4062C71.5469 38.5938 74.0703 40.1276 76.4453 42.0078C78.8698 43.8385 80.9974 46.0898 82.8281 48.7617C84.7083 51.3841 86.1927 54.4271 87.2812 57.8906C88.3698 61.3542 88.9141 65.263 88.9141 69.6172V114.148' +
3
+ 'ZM188.664 87.5781C188.664 90.25 188.194 93.1445 187.254 96.2617C186.314 99.3294 184.78 102.199 182.652 104.871C180.574 107.493 177.828 109.695 174.414 111.477C171.049 113.258 166.943 114.148 162.094 114.148H127.285C124.613 114.148 121.719 113.678 118.602 112.738C115.534 111.798 112.664 110.289 109.992 108.211C107.37 106.083 105.168 103.337 103.387 99.9727C101.605 96.5586 100.715 92.4271 100.715 87.5781C100.715 84.9062 101.185 82.0117 102.125 78.8945C103.065 75.7773 104.574 72.9076 106.652 70.2852C108.78 67.6133 111.526 65.3867 114.891 63.6055C118.305 61.8242 122.436 60.9336 127.285 60.9336H162.094V79.3398H127.285C124.663 79.3398 122.634 80.1562 121.199 81.7891C119.764 83.3724 119.047 85.3516 119.047 87.7266C119.047 90.25 119.863 92.2292 121.496 93.6641C123.178 95.0495 125.158 95.7422 127.434 95.7422H162.094C164.716 95.7422 166.745 94.9505 168.18 93.3672C169.615 91.7839 170.332 89.8047 170.332 87.4297V60.4883C170.332 57.9648 169.54 55.9609 167.957 54.4766C166.423 52.9922 164.469 52.25 162.094 52.25H119.715V33.918H162.094C164.766 33.918 167.635 34.388 170.703 35.3281C173.82 36.2682 176.69 37.8021 179.312 39.9297C181.984 42.0078 184.211 44.7539 185.992 48.168C187.773 51.5326 188.664 55.6393 188.664 60.4883V87.5781' +
4
+ 'ZM277.801 53.2148H244.105V114.148H224.586V53.2148H199.5V33.918H224.586V7.71875H244.105V33.918H277.801V53.2148' +
5
+ 'ZM310.086 17.4414H290.789V0H310.086V17.4414' +
6
+ 'ZM310.086 114.148H290.789V33.918H310.086V114.148' +
7
+ 'ZM424.383 33.918C422.206 40.6966 419.658 47.3763 416.738 53.957C413.819 60.5378 410.454 66.8216 406.645 72.8086C402.884 78.7461 398.654 84.263 393.953 89.3594C389.302 94.4062 384.132 98.7852 378.441 102.496C372.801 106.158 366.641 109.052 359.961 111.18C353.331 113.258 346.132 114.297 338.363 114.297C337.027 114.297 335.766 114.049 334.578 113.555C333.391 113.06 332.352 112.392 331.461 111.551C330.57 110.66 329.878 109.646 329.383 108.508C328.888 107.32 328.641 106.059 328.641 104.723V33.918H347.938V94.2578C352.638 94.2578 357.141 93.2435 361.445 91.2148C365.799 89.1862 369.906 86.5143 373.766 83.1992C377.625 79.8346 381.188 76.0247 384.453 71.7695C387.768 67.5143 390.712 63.1602 393.285 58.707C395.858 54.2044 398.035 49.8008 399.816 45.4961C401.647 41.1914 403.008 37.332 403.898 33.918H424.383' +
8
+ 'Z';
9
+ // Rail variant clips to the n glyph for the collapsed sidebar.
10
+ export function wordmark(opts = {}) {
11
+ const rail = opts.rail === true;
12
+ const cls = rail ? 'wordmark is-rail' : 'wordmark';
13
+ const box = rail ? '0 0 90 115' : '0 0 425 115';
14
+ return '<svg class="' + cls + '" viewBox="' + box + '" role="img" aria-label="nativ" ' +
15
+ 'focusable="false"><path fill="currentColor" d="' + WORDMARK_PATH + '"/></svg>';
16
+ }