@njeybe/nativ 2.6.0 → 2.7.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 (68) hide show
  1. package/CHANGELOG.md +35 -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/index.js +11 -0
  12. package/dist/server/studio-ui/pages/benchmarks/styles.js +8 -5
  13. package/dist/server/studio-ui/pages/database/browser.js +1 -1
  14. package/dist/server/studio-ui/pages/database/connection.js +1 -1
  15. package/dist/server/studio-ui/pages/database/data-shell.js +5 -5
  16. package/dist/server/studio-ui/pages/database/drift.js +1 -1
  17. package/dist/server/studio-ui/pages/database/explorer.js +2 -2
  18. package/dist/server/studio-ui/pages/database/styles.js +5 -5
  19. package/dist/server/studio-ui/pages/flow/styles.js +3 -3
  20. package/dist/server/studio-ui/pages/home/script.js +110 -91
  21. package/dist/server/studio-ui/pages/home/styles.js +33 -45
  22. package/dist/server/studio-ui/pages/tasks/script.js +150 -3
  23. package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
  24. package/dist/server/studio-ui/pages/team/script.js +49 -52
  25. package/dist/server/studio-ui/pages/team/styles.js +21 -20
  26. package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
  27. package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
  28. package/dist/server/studio-ui/shell/brand.d.ts +4 -0
  29. package/dist/server/studio-ui/shell/brand.js +16 -0
  30. package/dist/server/studio-ui/shell/console.js +61 -2
  31. package/dist/server/studio-ui/shell/core.js +8 -8
  32. package/dist/server/studio-ui/shell/detail-view.js +1 -1
  33. package/dist/server/studio-ui/shell/dispatch.js +18 -0
  34. package/dist/server/studio-ui/shell/events-tools.js +5 -1
  35. package/dist/server/studio-ui/shell/format.js +5 -6
  36. package/dist/server/studio-ui/shell/icons.js +23 -15
  37. package/dist/server/studio-ui/shell/markup-head.js +11 -6
  38. package/dist/server/studio-ui/shell/markup-tools.js +19 -10
  39. package/dist/server/studio-ui/shell/paint.js +3 -0
  40. package/dist/server/studio-ui/shell/pipeline-state.js +1 -1
  41. package/dist/server/studio-ui/shell/proposals.js +6 -2
  42. package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
  43. package/dist/server/studio-ui/shell/pulse.js +67 -0
  44. package/dist/server/studio-ui/shell/router.js +25 -0
  45. package/dist/server/studio-ui/shell/sidebar-view.js +6 -3
  46. package/dist/server/studio-ui/shell/toasts-format.js +4 -4
  47. package/dist/server/studio-ui/styles/console.js +33 -13
  48. package/dist/server/studio-ui/styles/dialogs.js +4 -4
  49. package/dist/server/studio-ui/styles/responsive.js +8 -0
  50. package/dist/server/studio-ui/styles/shell.js +24 -5
  51. package/dist/server/studio-ui/styles/strategist.js +10 -10
  52. package/dist/server/studio-ui/styles/tokens-base.js +23 -23
  53. package/dist/server/studio-ui.js +2 -0
  54. package/package.json +1 -1
  55. package/plugin/.claude-plugin/plugin.json +1 -1
  56. package/plugin/agents/architect.md +10 -11
  57. package/templates/GEMINI.md +3 -2
  58. package/templates/claude-agents/architect.md +10 -11
  59. package/templates/design/taste/LICENSE +21 -0
  60. package/templates/design/taste/SOURCE.md +28 -0
  61. package/templates/design/taste/v1/DIGEST.md +57 -0
  62. package/templates/design/taste/v1/SKILL.md +226 -0
  63. package/templates/design/taste/v2/DIGEST.md +62 -0
  64. package/templates/design/taste/v2/SKILL.md +1206 -0
  65. package/templates/dot-ai/subagents/architect.md +6 -0
  66. package/templates/dot-ai/subagents/flutter-developer.md +2 -11
  67. package/templates/dot-ai/subagents/frontend.md +47 -44
  68. package/templates/dot-ai/ui_specs.md +15 -36
@@ -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
+ }
@@ -57,11 +57,13 @@ export const consoleScript = String.raw ` // ─── Live runner console draw
57
57
  function syncConsole() {
58
58
  var run = rc.taskId ? runFor(rc.taskId) : null;
59
59
  var task = rc.taskId ? taskById(rc.taskId) : null;
60
- $('rc-task').textContent = rc.taskId || '–';
60
+ $('rc-task').textContent = rc.taskId || '';
61
61
  $('rc-title').textContent = task ? task.title : '';
62
62
  $('rc-steps').innerHTML = rcStepper(run);
63
63
  $('rc-abort').disabled = !isRunActive(run);
64
64
  $('rc-elapsed').textContent = mmss(elapsedOf(run) || 0);
65
+ $('rc-clock').textContent = $('rc-elapsed').textContent;
66
+ syncInspectorStatus(run);
65
67
  $('rc-bytes').textContent = formatBytes((run && run.logBytes) || 0);
66
68
  var gate = 'Idle';
67
69
  if (run && run.verification) gate = run.verification.success ? 'Passed' : 'Failed';
@@ -90,6 +92,7 @@ export const consoleScript = String.raw ` // ─── Live runner console draw
90
92
  var run = rc.taskId ? runFor(rc.taskId) : null;
91
93
  if (!rc.taskId || !isRunActive(run)) { clearInterval(rc.ticker); rc.ticker = null; return; }
92
94
  $('rc-elapsed').textContent = mmss(elapsedOf(run) || 0);
95
+ $('rc-clock').textContent = $('rc-elapsed').textContent;
93
96
  }, 1000);
94
97
  }
95
98
 
@@ -135,7 +138,7 @@ export const consoleScript = String.raw ` // ─── Live runner console draw
135
138
  $('rc-diff-files').innerHTML = (files || []).map(function (f) { return '<li title="' + esc(f) + '">' + esc(f) + '</li>'; }).join('');
136
139
  $('rc-diff-files').hidden = !(files && files.length);
137
140
  $('rc-diff-body').innerHTML = bodyHtml;
138
- $('rc-diff-count').textContent = count == null ? '–' : String(count);
141
+ $('rc-diff-count').textContent = count == null ? '-' : String(count);
139
142
  }
140
143
 
141
144
  function loadConsoleDiff(taskId) {
@@ -191,4 +194,60 @@ export const consoleScript = String.raw ` // ─── Live runner console draw
191
194
  return ['logs', 'diff', 'heal'].filter(function (name) { return !$('rc-tab-' + name).hidden; });
192
195
  }
193
196
 
197
+ // Inspector header status: dot color follows the run state only.
198
+ function syncInspectorStatus(run) {
199
+ var st = run ? run.status : '';
200
+ var tone = !st ? 'idle' : st === 'completed' ? 'ok' : (st === 'failed' || st === 'aborted') ? 'err' : 'run';
201
+ $('rc-status').setAttribute('data-tone', tone);
202
+ $('rc-status-text').textContent = st ? (RUN_LABEL[st] || st) : 'Idle';
203
+ }
204
+
205
+ // Below 1024px the inspector is a modal sheet over the content.
206
+ function inspectorIsSheet() {
207
+ return window.matchMedia && window.matchMedia('(max-width: 1023px)').matches;
208
+ }
209
+
210
+ // Remembers the opener so closing hands focus back to it.
211
+ function rememberInspectorOpener() {
212
+ var drawer = $('runner-console-drawer');
213
+ var active = document.activeElement;
214
+ if (drawer.hidden && active && !drawer.contains(active)) rc.opener = active;
215
+ }
216
+
217
+ function applyInspectorMode() {
218
+ var drawer = $('runner-console-drawer');
219
+ var sheet = inspectorIsSheet() && !drawer.hidden;
220
+ if (sheet) drawer.setAttribute('aria-modal', 'true');
221
+ else drawer.removeAttribute('aria-modal');
222
+ drawer.setAttribute('role', sheet ? 'dialog' : 'region');
223
+ if ($('task-drawer').hidden) $('scrim').hidden = !sheet;
224
+ }
225
+
226
+ function restoreInspectorFocus() {
227
+ if ($('task-drawer').hidden) $('scrim').hidden = true;
228
+ var opener = rc.opener;
229
+ rc.opener = null;
230
+ if (opener && document.contains(opener) && opener.focus) opener.focus();
231
+ }
232
+
233
+ // Keeps Tab inside the sheet while it is modal.
234
+ function trapInspectorFocus(e) {
235
+ var drawer = $('runner-console-drawer');
236
+ if (e.key !== 'Tab' || drawer.hidden || !inspectorIsSheet()) return;
237
+ var items = Array.prototype.filter.call(
238
+ drawer.querySelectorAll('button, [href], [tabindex]:not([tabindex="-1"]), textarea, input'),
239
+ function (el) { return !el.disabled && el.offsetParent !== null; });
240
+ if (!items.length) return;
241
+ var first = items[0], last = items[items.length - 1];
242
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
243
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
244
+ }
245
+
246
+ document.addEventListener('keydown', trapInspectorFocus);
247
+ document.addEventListener('click', function (e) {
248
+ var t = e.target && e.target.closest ? e.target.closest('#rc-open-diff') : null;
249
+ if (t) setConsoleTab('diff', true);
250
+ });
251
+ window.addEventListener('resize', applyInspectorMode);
252
+
194
253
  `;
@@ -6,14 +6,14 @@ export const coreScript = String.raw `<script>
6
6
  var $ = function (id) { return document.getElementById(id); };
7
7
 
8
8
  var ICON = {
9
- spark: '<svg class="icon t1-spark" width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1.5l1.6 4.9 4.9 1.6-4.9 1.6L8 14.5l-1.6-4.9L1.5 8l4.9-1.6z"/></svg>',
10
- check: '<svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>',
11
- x: '<svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg>',
12
- shield: '<svg class="icon" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 1.8l5.2 2.1v3.7c0 3.1-2.2 5.6-5.2 6.6-3-1-5.2-3.5-5.2-6.6V3.9z"/><path d="M5.6 8.1l1.7 1.7 3.2-3.3"/></svg>',
13
- play: '<svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M4.5 2.8v10.4L13 8z"/></svg>',
14
- alert: '<svg class="icon" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5.5M12 16.5v.01"/></svg>',
15
- branch: '<svg class="icon" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="6" cy="5" r="2"/><circle cx="6" cy="19" r="2"/><circle cx="18" cy="8" r="2"/><path d="M6 7v10M18 10c0 4-6 3-11.2 7.4"/></svg>',
16
- gauge: '<svg class="icon" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M4 16a8 8 0 1 1 16 0"/><path d="M12 16l4-5"/></svg>'
9
+ spark: '<svg class="icon t1-spark" width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z"/></svg>',
10
+ check: '<svg class="icon" width="12" height="12" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"/></svg>',
11
+ x: '<svg class="icon" width="12" height="12" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>',
12
+ shield: '<svg class="icon" width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M208,40H48A16,16,0,0,0,32,56v56c0,52.72,25.52,84.67,46.93,102.19,23.06,18.86,46,25.26,47,25.53a8,8,0,0,0,4.2,0c1-.27,23.91-6.67,47-25.53C198.48,196.67,224,164.72,224,112V56A16,16,0,0,0,208,40Zm0,72c0,37.07-13.66,67.16-40.6,89.42A129.3,129.3,0,0,1,128,223.62a128.25,128.25,0,0,1-38.92-21.81C61.82,179.51,48,149.3,48,112l0-56,160,0ZM82.34,141.66a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32l-56,56a8,8,0,0,1-11.32,0Z"/></svg>',
13
+ play: '<svg class="icon" width="12" height="12" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M232.4,114.49,88.32,26.35a16,16,0,0,0-16.2-.3A15.86,15.86,0,0,0,64,39.87V216.13A15.94,15.94,0,0,0,80,232a16.07,16.07,0,0,0,8.36-2.35L232.4,141.51a15.81,15.81,0,0,0,0-27ZM80,215.94V40l143.83,88Z"/></svg>',
14
+ alert: '<svg class="icon" width="28" height="28" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"/></svg>',
15
+ branch: '<svg class="icon" width="28" height="28" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M232,64a32,32,0,1,0-40,31v17a8,8,0,0,1-8,8H96a23.84,23.84,0,0,0-8,1.38V95a32,32,0,1,0-16,0v66a32,32,0,1,0,16,0V144a8,8,0,0,1,8-8h88a24,24,0,0,0,24-24V95A32.06,32.06,0,0,0,232,64ZM64,64A16,16,0,1,1,80,80,16,16,0,0,1,64,64ZM96,192a16,16,0,1,1-16-16A16,16,0,0,1,96,192ZM200,80a16,16,0,1,1,16-16A16,16,0,0,1,200,80Z"/></svg>',
16
+ gauge: '<svg class="icon" width="28" height="28" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M207.06,72.67A111.24,111.24,0,0,0,128,40h-.4C66.07,40.21,16,91,16,153.13V176a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V152A111.25,111.25,0,0,0,207.06,72.67ZM224,176H119.71l54.76-75.3a8,8,0,0,0-12.94-9.42L99.92,176H32V153.13c0-3.08.15-6.12.43-9.13H56a8,8,0,0,0,0-16H35.27c10.32-38.86,44-68.24,84.73-71.66V80a8,8,0,0,0,16,0V56.33A96.14,96.14,0,0,1,221,128H200a8,8,0,0,0,0,16h23.67c.21,2.65.33,5.31.33,8Z"/></svg>'
17
17
  };
18
18
 
19
19
  function formatBytes(n) {
@@ -47,7 +47,7 @@ export const detailViewScript = String.raw ` // ─── Task detail panel (pu
47
47
 
48
48
  function detailFacts(t, model) {
49
49
  var m = model.milestones.filter(function (x) { return x.id === t.milestoneId; })[0];
50
- var ms = m ? '<button type="button" class="chip" data-ms="' + esc(m.id) + '"><span class="mono">' + esc(m.id) + '</span>' + esc(m.short) + '</button>' : '–';
50
+ var ms = m ? '<button type="button" class="chip" data-ms="' + esc(m.id) + '"><span class="mono">' + esc(m.id) + '</span>' + esc(m.short) + '</button>' : '-';
51
51
  return '<dl class="kv"><dt>Milestone</dt><dd>' + ms + '</dd><dt>Agent</dt><dd>' + ui.agentChip(t.agent) + '</dd>' +
52
52
  '<dt>Started</dt><dd class="num">' + esc(fmt.full(t.startT)) + '</dd><dt>Took</dt><dd class="num">' + detailTook(t) + '</dd>' +
53
53
  '<dt>Est. cost</dt><dd class="num">' + esc(fmt.money(t.costUsd)) + '</dd></dl>';
@@ -147,4 +147,22 @@ export const dispatchScript = String.raw ` // ─── Autonomous dispatch: PO
147
147
  runDispatch(id, opts);
148
148
  }
149
149
 
150
+ // ─── Top bar: Dispatch next ────────────────────────────────────────────────
151
+ var nextBtn = $('btn-dispatch-next'), nextId = null, nextSeen = null;
152
+ function syncDispatchNext() {
153
+ var m = Studio.model;
154
+ if (!m) return;
155
+ var t = m.tasks.filter(function (x) { return x.status === 'pending' && x.isAvailable; })[0];
156
+ var key = t ? t.id : '';
157
+ if (key === nextSeen) return;
158
+ nextSeen = key;
159
+ nextId = t ? t.id : null;
160
+ nextBtn.disabled = !t;
161
+ $('dispatch-next-label').textContent = t ? 'Dispatch ' + t.id : 'Nothing ready';
162
+ nextBtn.title = t ? 'Open the dispatch dialog for ' + t.id : 'No pending task has its dependencies met';
163
+ }
164
+ nextBtn.addEventListener('click', function () { if (nextId) openDispatchDialog(nextId); });
165
+ Studio.onPaint(syncDispatchNext);
166
+ syncDispatchNext();
167
+
150
168
  `;
@@ -81,7 +81,11 @@ export const eventsToolsScript = String.raw ` // ─── Events: live runner
81
81
  $('rc-minimize').addEventListener('click', toggleConsoleMinimized);
82
82
  $('rc-close').addEventListener('click', closeConsole);
83
83
  $('runner-console-drawer').addEventListener('keydown', function (e) {
84
- if (e.key === 'Escape') { e.stopPropagation(); closeConsole(); $('main').focus(); }
84
+ if (e.key !== 'Escape') return;
85
+ e.stopPropagation();
86
+ var opener = rc.opener;
87
+ closeConsole();
88
+ if (!(opener && document.contains(opener))) $('main').focus();
85
89
  });
86
90
 
87
91
  $('btn-confirm-ok').addEventListener('click', function () {
@@ -50,7 +50,7 @@ export const formatScript = String.raw ` // ─── Studio client core: state
50
50
 
51
51
  var fmt = {
52
52
  dur: function (ms) {
53
- if (ms == null || isNaN(ms)) return '–';
53
+ if (ms == null || isNaN(ms)) return '-';
54
54
  var s = Math.round(ms / 1000);
55
55
  if (s < 60) return s + 's';
56
56
  if (s < 3600) return Math.floor(s / 60) + 'm ' + pad2(s % 60) + 's';
@@ -70,20 +70,19 @@ export const formatScript = String.raw ` // ─── Studio client core: state
70
70
  if (!t) return 'not recorded';
71
71
  return new Date(t).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
72
72
  },
73
- money: function (v) { return v == null || isNaN(v) ? '–' : '$' + Number(v).toFixed(2); }
73
+ money: function (v) { return v == null || isNaN(v) ? '-' : '$' + Number(v).toFixed(2); }
74
74
  };
75
75
 
76
76
  /** Grounded spend is small per task: four decimals below a dollar. */
77
77
  function usdSpend(n) {
78
- if (n == null || isNaN(n)) return '–';
78
+ if (n == null || isNaN(n)) return '-';
79
79
  n = Number(n);
80
80
  return '$' + (n >= 1 ? n.toFixed(2) : n.toFixed(4));
81
81
  }
82
82
 
83
- /** Wraps inner SVG paths (24-unit box, stroke icon) in an svg element. */
83
+ /** Wraps inner SVG paths (256-unit box, fill icon) in an svg element. */
84
84
  function svgWrap(inner, width) {
85
- return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="' + (width || 1.8) +
86
- '" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + (inner || '') + '</svg>';
85
+ return '<svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">' + (inner || '') + '</svg>';
87
86
  }
88
87
  function svgIcon(name, width) { return svgWrap(Studio.icons[name], width); }
89
88
 
@@ -1,20 +1,28 @@
1
- // Inline SVG icon paths and status marks shared by the shell and every page.
1
+ // Phosphor Regular (MIT) icon paths and status marks shared by the shell and pages.
2
2
  export const ICON_PATHS = {
3
- home: '<path d="M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/>',
4
- tasks: '<rect x="3" y="4" width="5" height="16" rx="1.5"/><rect x="10" y="4" width="5" height="11" rx="1.5"/><rect x="17" y="4" width="4" height="7" rx="1.5"/>',
5
- flow: '<rect x="2" y="4" width="6" height="5" rx="1.5"/><rect x="16" y="4" width="6" height="5" rx="1.5"/><rect x="9" y="15" width="6" height="5" rx="1.5"/><path d="M8 6.5h8M5 9v3.5a2.5 2.5 0 0 0 2.5 2.5H9m10-6v3.5a2.5 2.5 0 0 1-2.5 2.5H15"/>',
6
- team: '<circle cx="9" cy="8" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0"/><circle cx="17.5" cy="9" r="2.5"/><path d="M16 14.2A5 5 0 0 1 22 19"/>',
7
- worktrees: '<circle cx="6" cy="5" r="2.2"/><circle cx="6" cy="19" r="2.2"/><circle cx="18" cy="7" r="2.2"/><path d="M6 7.2v9.6M18 9.2c0 4-6 3-10.5 7.5"/>',
8
- benchmarks: '<path d="M4 17a8 8 0 1 1 16 0"/><path d="m12 17 4-5"/>',
9
- database: '<ellipse cx="12" cy="5.5" rx="7.5" ry="2.8"/><path d="M4.5 5.5v13c0 1.5 3.4 2.8 7.5 2.8s7.5-1.3 7.5-2.8v-13M4.5 12c0 1.5 3.4 2.8 7.5 2.8s7.5-1.3 7.5-2.8"/>',
10
- search: '<circle cx="11" cy="11" r="6.5"/><path d="m20 20-4.2-4.2"/>',
11
- sun: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>',
12
- x: '<path d="M6 6l12 12M18 6 6 18"/>',
13
- chev: '<path d="m9 6 6 6-6 6"/>',
3
+ home: '<path d="M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z"/>',
4
+ tasks: '<path d="M224,128a8,8,0,0,1-8,8H128a8,8,0,0,1,0-16h88A8,8,0,0,1,224,128ZM128,72h88a8,8,0,0,0,0-16H128a8,8,0,0,0,0,16Zm88,112H128a8,8,0,0,0,0,16h88a8,8,0,0,0,0-16ZM82.34,42.34,56,68.69,45.66,58.34A8,8,0,0,0,34.34,69.66l16,16a8,8,0,0,0,11.32,0l32-32A8,8,0,0,0,82.34,42.34Zm0,64L56,132.69,45.66,122.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Zm0,64L56,196.69,45.66,186.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Z"/>',
5
+ flow: '<path d="M245.66,74.34l-32-32a8,8,0,0,0-11.32,11.32L220.69,72H208c-49.33,0-61.05,28.12-71.38,52.92-9.38,22.51-16.92,40.59-49.48,42.84a40,40,0,1,0,.1,16c43.26-2.65,54.34-29.15,64.14-52.69C161.41,107,169.33,88,208,88h12.69l-18.35,18.34a8,8,0,0,0,11.32,11.32l32-32A8,8,0,0,0,245.66,74.34ZM48,200a24,24,0,1,1,24-24A24,24,0,0,1,48,200Z"/>',
6
+ team: '<path d="M244.8,150.4a8,8,0,0,1-11.2-1.6A51.6,51.6,0,0,0,192,128a8,8,0,0,1-7.37-4.89,8,8,0,0,1,0-6.22A8,8,0,0,1,192,112a24,24,0,1,0-23.24-30,8,8,0,1,1-15.5-4A40,40,0,1,1,219,117.51a67.94,67.94,0,0,1,27.43,21.68A8,8,0,0,1,244.8,150.4ZM190.92,212a8,8,0,1,1-13.84,8,57,57,0,0,0-98.16,0,8,8,0,1,1-13.84-8,72.06,72.06,0,0,1,33.74-29.92,48,48,0,1,1,58.36,0A72.06,72.06,0,0,1,190.92,212ZM128,176a32,32,0,1,0-32-32A32,32,0,0,0,128,176ZM72,120a8,8,0,0,0-8-8A24,24,0,1,1,87.24,82a8,8,0,1,0,15.5-4A40,40,0,1,0,37,117.51,67.94,67.94,0,0,0,9.6,139.19a8,8,0,1,0,12.8,9.61A51.6,51.6,0,0,1,64,128,8,8,0,0,0,72,120Z"/>',
7
+ worktrees: '<path d="M232,64a32,32,0,1,0-40,31v17a8,8,0,0,1-8,8H96a23.84,23.84,0,0,0-8,1.38V95a32,32,0,1,0-16,0v66a32,32,0,1,0,16,0V144a8,8,0,0,1,8-8h88a24,24,0,0,0,24-24V95A32.06,32.06,0,0,0,232,64ZM64,64A16,16,0,1,1,80,80,16,16,0,0,1,64,64ZM96,192a16,16,0,1,1-16-16A16,16,0,0,1,96,192ZM200,80a16,16,0,1,1,16-16A16,16,0,0,1,200,80Z"/>',
8
+ benchmarks: '<path d="M207.06,72.67A111.24,111.24,0,0,0,128,40h-.4C66.07,40.21,16,91,16,153.13V176a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V152A111.25,111.25,0,0,0,207.06,72.67ZM224,176H119.71l54.76-75.3a8,8,0,0,0-12.94-9.42L99.92,176H32V153.13c0-3.08.15-6.12.43-9.13H56a8,8,0,0,0,0-16H35.27c10.32-38.86,44-68.24,84.73-71.66V80a8,8,0,0,0,16,0V56.33A96.14,96.14,0,0,1,221,128H200a8,8,0,0,0,0,16h23.67c.21,2.65.33,5.31.33,8Z"/>',
9
+ database: '<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"/>',
10
+ search: '<path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"/>',
11
+ sun: '<path d="M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z"/>',
12
+ x: '<path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/>',
13
+ chev: '<path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"/>',
14
+ play: '<path d="M232.4,114.49,88.32,26.35a16,16,0,0,0-16.2-.3A15.86,15.86,0,0,0,64,39.87V216.13A15.94,15.94,0,0,0,80,232a16.07,16.07,0,0,0,8.36-2.35L232.4,141.51a15.81,15.81,0,0,0,0-27ZM80,215.94V40l143.83,88Z"/>',
15
+ stop: '<path d="M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,160H56V56H200V200Z"/>',
16
+ warning: '<path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"/>',
17
+ check: '<path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/>',
18
+ terminal: '<path d="M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z"/>',
19
+ pause: '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216ZM112,96v64a8,8,0,0,1-16,0V96a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V96a8,8,0,0,1,16,0Z"/>',
20
+ moon: '<path d="M233.54,142.23a8,8,0,0,0-8-2,88.08,88.08,0,0,1-109.8-109.8,8,8,0,0,0-10-10,104.84,104.84,0,0,0-52.91,37A104,104,0,0,0,136,224a103.09,103.09,0,0,0,62.52-20.88,104.84,104.84,0,0,0,37-52.91A8,8,0,0,0,233.54,142.23ZM188.9,190.34A88,88,0,0,1,65.66,67.11a89,89,0,0,1,31.4-26A106,106,0,0,0,96,56,104.11,104.11,0,0,0,200,160a106,106,0,0,0,14.92-1.06A89,89,0,0,1,188.9,190.34Z"/>',
21
+ sidebar: '<path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM40,152H56a8,8,0,0,0,0-16H40V120H56a8,8,0,0,0,0-16H40V88H56a8,8,0,0,0,0-16H40V56H80V200H40Zm176,48H96V56H216V200Z"/>',
14
22
  };
15
23
  // Done check, up-next dashed ring and stuck alert; running is a pulsing dot.
16
24
  export const ST_SVG = {
17
- completed: '<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="currentColor" opacity=".15"/><path d="m5 8.2 2 2 4-4.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
18
- pending: '<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="5.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="2.5 2"/></svg>',
19
- blocked: '<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="currentColor" opacity=".15"/><path d="M8 4.5v4.2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><circle cx="8" cy="11.3" r="1" fill="currentColor"/></svg>',
25
+ completed: '<svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/></svg>',
26
+ pending: '<svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M96.26,37.05A8,8,0,0,1,102,27.29a104.11,104.11,0,0,1,52,0,8,8,0,0,1-2,15.75,8.15,8.15,0,0,1-2-.26,88.09,88.09,0,0,0-44,0A8,8,0,0,1,96.26,37.05ZM53.79,55.14a104.05,104.05,0,0,0-26,45,8,8,0,0,0,15.42,4.27,88,88,0,0,1,22-38.09A8,8,0,0,0,53.79,55.14ZM43.21,151.55a8,8,0,1,0-15.42,4.28,104.12,104.12,0,0,0,26,45,8,8,0,0,0,11.41-11.22A88.14,88.14,0,0,1,43.21,151.55ZM150,213.22a88,88,0,0,1-44,0,8,8,0,1,0-4,15.49,104.11,104.11,0,0,0,52,0,8,8,0,0,0-4-15.49ZM222.65,146a8,8,0,0,0-9.85,5.58,87.91,87.91,0,0,1-22,38.08,8,8,0,1,0,11.42,11.21,104,104,0,0,0,26-45A8,8,0,0,0,222.65,146Zm-9.86-41.54a8,8,0,0,0,15.42-4.28,104,104,0,0,0-26-45,8,8,0,1,0-11.41,11.22A88,88,0,0,1,212.79,104.45Z"/></svg>',
27
+ blocked: '<svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"/></svg>',
20
28
  };
@@ -1,5 +1,6 @@
1
1
  import { ICON_PATHS } from './icons.js';
2
- const svg = (name) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ICON_PATHS[name]}</svg>`;
2
+ import { wordmark } from './brand.js';
3
+ const svg = (name) => `<svg viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">${ICON_PATHS[name]}</svg>`;
3
4
  // Document head close, sidebar frame and top bar (needs version and project name).
4
5
  export function markupHead(version, projectName, projectHidden) {
5
6
  return String.raw `</head>
@@ -8,34 +9,38 @@ export function markupHead(version, projectName, projectHidden) {
8
9
 
9
10
  <div class="app">
10
11
  <aside class="side" aria-label="Studio navigation">
11
- <div class="brand"><span class="logo">N</span><b>Nativ Studio</b><span class="ver">v${version}</span></div>
12
+ <div class="brand">${wordmark()}${wordmark({ rail: true })}<b>Studio</b></div>
12
13
  <div id="side-nav"></div>
13
14
  <div class="side-foot">
14
15
  <span class="foot-project" id="project-name"${projectHidden}>${projectName}</span>
15
16
  <div id="side-foot"></div>
17
+ <span class="ver">v${version}</span>
16
18
  </div>
17
19
  </aside>
18
20
 
19
21
  <div class="main">
20
22
  <header class="top">
21
23
  <span class="crumb">Studio / <b id="crumb-view">Home</b></span>
24
+ <span class="spacer" aria-hidden="true"></span>
22
25
  <div class="top-right">
23
- <button class="btn" id="btn-find" type="button" aria-keyshortcuts="Control+K Meta+K">${svg('search')}<span>Find task</span><span class="kbd">Ctrl K</span></button>
26
+ <button class="btn" id="btn-find" type="button" aria-keyshortcuts="Control+K Meta+K">${svg('search')}<span>Find task</span><span class="kbd">Ctrl+K</span></button>
27
+ <button class="btn ghost" id="btn-theme" type="button" title="Switch light or dark" aria-label="Switch light or dark"><span class="ic-sun">${svg('sun')}</span><span class="ic-moon">${svg('moon')}</span></button>
28
+ <button class="btn primary" id="btn-dispatch-next" type="button" disabled title="Loading tasks">${svg('play')}<span id="dispatch-next-label">Nothing ready</span></button>
24
29
  <div class="t1" id="t1">
25
- <button class="t1-chip" id="t1-chip" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="t1-menu" data-state="idle" title="Tier 1 AI Strategist"><svg class="icon t1-spark" width="14" height="14" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 1.5l1.6 4.9 4.9 1.6-4.9 1.6L8 14.5l-1.6-4.9L1.5 8l4.9-1.6z"/></svg><span class="t1-chip-label">Tier 1 AI:</span><span class="t1-chip-model" id="t1-chip-model">Gemini 3.8 Flash</span><svg class="icon t1-caret" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg></button>
30
+ <button class="t1-chip" id="t1-chip" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="t1-menu" data-state="idle" title="Tier 1 AI Strategist"><svg class="icon t1-spark" width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M197.58,129.06,146,110l-19-51.62a15.92,15.92,0,0,0-29.88,0L78,110l-51.62,19a15.92,15.92,0,0,0,0,29.88L78,178l19,51.62a15.92,15.92,0,0,0,29.88,0L146,178l51.62-19a15.92,15.92,0,0,0,0-29.88ZM137,164.22a8,8,0,0,0-4.74,4.74L112,223.85,91.78,169A8,8,0,0,0,87,164.22L32.15,144,87,123.78A8,8,0,0,0,91.78,119L112,64.15,132.22,119a8,8,0,0,0,4.74,4.74L191.85,144ZM144,40a8,8,0,0,1,8-8h16V16a8,8,0,0,1,16,0V32h16a8,8,0,0,1,0,16H184V64a8,8,0,0,1-16,0V48H152A8,8,0,0,1,144,40ZM248,88a8,8,0,0,1-8,8h-8v8a8,8,0,0,1-16,0V96h-8a8,8,0,0,1,0-16h8V72a8,8,0,0,1,16,0v8h8A8,8,0,0,1,248,88Z"/></svg><span class="t1-chip-label">Tier 1 AI:</span><span class="t1-chip-model" id="t1-chip-model">Gemini 3.8 Flash</span><svg class="icon t1-caret" width="12" height="12" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"/></svg></button>
26
31
  <div class="t1-menu" id="t1-menu" role="dialog" aria-label="Tier 1 AI Strategist" tabindex="-1" hidden>
27
32
  <div class="t1-row"><span>Active model</span><code id="t1-model">gemini-3.8-flash</code></div>
28
33
  <div class="t1-row"><span>Last evaluation</span><b id="t1-latency">No evaluations yet</b></div>
29
34
  <div class="t1-row"><span>Gemini API key</span><b id="t1-key">Checking</b></div>
30
35
  <div class="t1-row"><span id="t1-auto-label">Autonomous Auto-Triage</span><button class="t1-switch" id="t1-auto" type="button" role="switch" aria-checked="false" aria-labelledby="t1-auto-label" title="When on, fixes Tier 1 proves safe are applied without asking" disabled>OFF</button></div>
31
- <p class="t1-policy"><svg class="icon" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 1.8l5.2 2.1v3.7c0 3.1-2.2 5.6-5.2 6.6-3-1-5.2-3.5-5.2-6.6V3.9z"/><path d="M5.6 8.1l1.7 1.7 3.2-3.3"/></svg>Always halts on destructive database operations &amp; major API contract breaks.</p>
36
+ <p class="t1-policy"><svg class="icon" width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M208,40H48A16,16,0,0,0,32,56v56c0,52.72,25.52,84.67,46.93,102.19,23.06,18.86,46,25.26,47,25.53a8,8,0,0,0,4.2,0c1-.27,23.91-6.67,47-25.53C198.48,196.67,224,164.72,224,112V56A16,16,0,0,0,208,40Zm0,72c0,37.07-13.66,67.16-40.6,89.42A129.3,129.3,0,0,1,128,223.62a128.25,128.25,0,0,1-38.92-21.81C61.82,179.51,48,149.3,48,112l0-56,160,0ZM82.34,141.66a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32l-56,56a8,8,0,0,1-11.32,0Z"/></svg>Always halts on destructive database operations &amp; major API contract breaks.</p>
32
37
  <dl class="t1-stats"><div><dt>Evaluated</dt><dd id="t1-evaluated">0</dd></div><div><dt>Auto-resolved</dt><dd id="t1-resolved">0</dd></div><div><dt>Human-escalated</dt><dd id="t1-human">0</dd></div></dl>
33
38
  </div>
34
39
  </div>
35
- <button class="btn ghost" id="btn-theme" type="button" title="Switch light or dark" aria-label="Switch light or dark">${svg('sun')}</button>
36
40
  <span class="live" id="live" data-state="connecting" role="status" aria-live="polite"><i class="live-dot" id="live-dot" aria-hidden="true"></i><span id="live-label">Connecting</span></span>
37
41
  </div>
38
42
  </header>
43
+ <div id="pulse" class="pulse-strip" aria-live="polite"></div>
39
44
  <p class="banner" id="banner" role="alert" hidden>Studio lost contact with nativ. Retrying. <button class="btn small" id="banner-retry" type="button">Retry</button></p>
40
45
 
41
46
  <main class="page" id="main" tabindex="-1">
@@ -1,4 +1,8 @@
1
- // Console drawer, dialogs and other shell overlays.
1
+ // Inspector pane, dialogs and other shell overlays.
2
+ import { ICON_PATHS } from './icons.js';
3
+ // Phosphor icon in the shared 256-unit format.
4
+ const icon = (name) => '<svg class="icon" width="14" height="14" viewBox="0 0 256 256" fill="currentColor"'
5
+ + ' aria-hidden="true">' + ICON_PATHS[name] + '</svg>';
2
6
  export const markupTools = String.raw `</main>
3
7
  </div>
4
8
  </div>
@@ -8,21 +12,26 @@ export const markupTools = String.raw `</main>
8
12
 
9
13
  <section id="runner-console-drawer" aria-labelledby="rc-task" hidden>
10
14
  <header class="rc-head">
11
- <code class="rc-task" id="rc-task">&#8211;</code>
15
+ <div class="rc-id-row">
16
+ <code class="rc-task" id="rc-task"></code>
17
+ <span class="rc-status" id="rc-status"><span class="rc-dot" aria-hidden="true"></span><span id="rc-status-text">Idle</span></span>
18
+ <code class="rc-clock" id="rc-clock">00:00</code>
19
+ </div>
12
20
  <span class="rc-title" id="rc-title"></span>
13
21
  <ol class="rc-steps" id="rc-steps" aria-label="Runner lifecycle"></ol>
14
22
  <div class="rc-controls">
23
+ <button class="rc-btn danger" type="button" id="rc-abort" aria-label="Abort the running agent">Abort</button>
24
+ <button class="rc-btn" type="button" id="rc-open-diff" aria-label="Open the worktree diff">Open diff</button>
15
25
  <button class="rc-btn" type="button" id="rc-autoscroll" aria-pressed="true" aria-label="Toggle console auto-scroll">Auto-scroll</button>
16
26
  <button class="rc-btn" type="button" id="rc-clear" aria-label="Clear the console buffer">Clear</button>
17
- <button class="rc-btn danger" type="button" id="rc-abort" aria-label="Abort the running agent">Abort</button>
18
- <button class="rc-btn" type="button" id="rc-minimize" aria-expanded="true" aria-controls="rc-body">Minimize</button>
27
+ <button class="rc-btn" type="button" id="rc-minimize" aria-expanded="true" aria-controls="rc-body" aria-label="Collapse the inspector">Collapse</button>
19
28
  <button class="rc-btn" type="button" id="rc-close" aria-label="Close the runner console">Close</button>
20
29
  </div>
21
30
  </header>
22
31
  <div class="rc-tabs" role="tablist" aria-label="Console views">
23
- <button class="rc-tab" type="button" role="tab" id="rc-tab-logs" data-rc-tab="logs" aria-controls="rc-body" aria-selected="true"><svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4.5l3.5 3.5L3 11.5M8.5 12h4.5"/></svg>Live Logs</button>
24
- <button class="rc-tab" type="button" role="tab" id="rc-tab-diff" data-rc-tab="diff" aria-controls="rc-diff" aria-selected="false" tabindex="-1"><svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M5 2.5v6M2 5.5h6M8.5 12.5h5.5"/></svg>Worktree Changes <span class="rc-count" id="rc-diff-count">&#8211;</span></button>
25
- <button class="rc-tab" type="button" role="tab" id="rc-tab-heal" data-rc-tab="heal" aria-controls="rc-heal" aria-selected="false" tabindex="-1" hidden><svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 1.8l5.2 2.1v3.7c0 3.1-2.2 5.6-5.2 6.6-3-1-5.2-3.5-5.2-6.6V3.9z"/><path d="M5.6 8.1l1.7 1.7 3.2-3.3"/></svg>Self-Healing Proposal <span class="rc-count is-proposal" id="rc-heal-count">1</span></button>
32
+ <button class="rc-tab" type="button" role="tab" id="rc-tab-logs" data-rc-tab="logs" aria-controls="rc-body" aria-selected="true">${icon('terminal')}Live Logs</button>
33
+ <button class="rc-tab" type="button" role="tab" id="rc-tab-diff" data-rc-tab="diff" aria-controls="rc-diff" aria-selected="false" tabindex="-1">${icon('worktrees')}Worktree Changes <span class="rc-count" id="rc-diff-count">-</span></button>
34
+ <button class="rc-tab" type="button" role="tab" id="rc-tab-heal" data-rc-tab="heal" aria-controls="rc-heal" aria-selected="false" tabindex="-1" hidden>${icon('check')}Self-Healing Proposal <span class="rc-count is-proposal" id="rc-heal-count">1</span></button>
26
35
  </div>
27
36
  <pre class="rc-body" id="rc-body" role="log" aria-live="polite" aria-label="Runner output" tabindex="0"></pre>
28
37
  <div class="rc-diff" id="rc-diff" role="tabpanel" aria-labelledby="rc-tab-diff" hidden>
@@ -57,8 +66,8 @@ export const markupTools = String.raw `</main>
57
66
  <fieldset class="choice-group">
58
67
  <legend>Engine</legend>
59
68
  <div class="engine-options">
60
- <label class="engine-option"><input type="radio" name="dispatch-engine" id="dispatch-engine-native" value="native" checked><svg class="icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 1.5L3.5 9H8l-1 5.5L12.5 7H8z"/></svg><span><b>Native Engine</b><span>Direct API, Prompt Caching &amp; Fast Streaming. Token spend is reported live.</span></span></label>
61
- <label class="engine-option"><input type="radio" name="dispatch-engine" id="dispatch-engine-cli" value="cli"><svg class="icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5"/><path d="M4.5 6.5L6.5 8l-2 1.5M8.5 10h3"/></svg><span><b>CLI Terminal Pairing</b><span>Interactive Claude Code in a terminal shell, driven by the command below.</span></span></label>
69
+ <label class="engine-option"><input type="radio" name="dispatch-engine" id="dispatch-engine-native" value="native" checked>${icon('play')}<span><b>Native Engine</b><span>Direct API, Prompt Caching &amp; Fast Streaming. Token spend is reported live.</span></span></label>
70
+ <label class="engine-option"><input type="radio" name="dispatch-engine" id="dispatch-engine-cli" value="cli">${icon('terminal')}<span><b>CLI Terminal Pairing</b><span>Interactive Claude Code in a terminal shell, driven by the command below.</span></span></label>
62
71
  </div>
63
72
  </fieldset>
64
73
  <fieldset class="choice-group">
@@ -83,7 +92,7 @@ export const markupTools = String.raw `</main>
83
92
  <p class="field-error" id="dispatch-error" aria-live="polite"></p>
84
93
  <div class="modal-foot">
85
94
  <button class="btn" value="cancel" type="submit">Cancel</button>
86
- <button class="btn primary" type="button" id="btn-launch-agent" aria-keyshortcuts="Control+Enter"><svg class="icon" width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M4.5 2.8v10.4L13 8z"/></svg>Launch Autonomous Runner <kbd>Ctrl+Enter</kbd></button>
95
+ <button class="btn primary" type="button" id="btn-launch-agent" aria-keyshortcuts="Control+Enter">${icon('play')}Launch Autonomous Runner <kbd>Ctrl+Enter</kbd></button>
87
96
  </div>
88
97
  </form>
89
98
  </dialog>
@@ -79,7 +79,10 @@ export const paintScript = String.raw ` // ─── Painting ─────
79
79
  if (foot.__html !== footHtml) { foot.innerHTML = footHtml; foot.__html = footHtml; }
80
80
  var lost = !!(pipe.errors.tasks || pipe.errors.status) || live.mode === 'offline';
81
81
  $('banner').hidden = !lost;
82
+ Studio.afterPaint.forEach(function (fn) { try { fn(); } catch (e) { /* one hook must not break painting */ } });
82
83
  }
84
+ Studio.afterPaint = Studio.afterPaint || [];
85
+ Studio.onPaint = function (fn) { Studio.afterPaint.push(fn); };
83
86
 
84
87
  /** One entry point after data or state changes: model, sidebar, current page, panel, palette. */
85
88
  function refreshUi(parts) {
@@ -119,7 +119,7 @@ export const pipelineStateScript = String.raw ` // ─── Pipeline state ─
119
119
 
120
120
  /** 1234 becomes "1.2k" so the sidebar badge stays small. */
121
121
  function compactNum(n) {
122
- if (!isFinite(n)) return '–';
122
+ if (!isFinite(n)) return '-';
123
123
  if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
124
124
  if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, '') + 'k';
125
125
  return String(Math.round(n));
@@ -111,6 +111,7 @@ export const proposalsScript = String.raw ` // ─── Console tab 3: self-he
111
111
 
112
112
  function openConsole(taskId, quiet, tab) {
113
113
  var drawer = $('runner-console-drawer');
114
+ rememberInspectorOpener();
114
115
  if (rc.taskId !== taskId) {
115
116
  rc.taskId = taskId;
116
117
  rc.backfilled = false;
@@ -130,10 +131,11 @@ export const proposalsScript = String.raw ` // ─── Console tab 3: self-he
130
131
  rc.minimized = false;
131
132
  drawer.hidden = false;
132
133
  drawer.classList.remove('is-min');
133
- $('rc-minimize').textContent = 'Minimize';
134
+ $('rc-minimize').textContent = 'Collapse';
134
135
  $('rc-minimize').setAttribute('aria-expanded', 'true');
135
136
  document.body.classList.add('has-console');
136
137
  document.body.classList.remove('has-console-min');
138
+ applyInspectorMode();
137
139
  syncConsole();
138
140
  syncHealTab();
139
141
  rcTick();
@@ -149,6 +151,8 @@ export const proposalsScript = String.raw ` // ─── Console tab 3: self-he
149
151
  rc.diffTimer = null;
150
152
  $('runner-console-drawer').hidden = true;
151
153
  document.body.classList.remove('has-console', 'has-console-min');
154
+ applyInspectorMode();
155
+ restoreInspectorFocus();
152
156
  }
153
157
 
154
158
  function toggleConsoleMinimized() {
@@ -158,7 +162,7 @@ export const proposalsScript = String.raw ` // ─── Console tab 3: self-he
158
162
  document.body.classList.toggle('has-console', !rc.minimized);
159
163
  document.body.classList.toggle('has-console-min', rc.minimized);
160
164
  var btn = $('rc-minimize');
161
- btn.textContent = rc.minimized ? 'Expand' : 'Minimize';
165
+ btn.textContent = rc.minimized ? 'Expand' : 'Collapse';
162
166
  btn.setAttribute('aria-expanded', String(!rc.minimized));
163
167
  if (!rc.minimized && rc.tab === 'diff' && rc.taskId) loadConsoleDiff(rc.taskId);
164
168
  }
@@ -0,0 +1 @@
1
+ export declare const pulseScript: string;
@@ -0,0 +1,67 @@
1
+ // Pulse strip: active milestone plus running, blocked and decision counts under the header.
2
+ export const pulseScript = String.raw ` // ─── Pulse strip ───────────────────────────────────────────────────────────
3
+ /** Pure: turns raw counts into the strip's parts. in = { loaded, offline, ms, running, blocked, decisions }. */
4
+ function pulseParts(s) {
5
+ if (s.offline) return { kind: 'offline', text: 'Offline, retrying' };
6
+ if (!s.loaded) return { kind: 'loading', text: '-- -- -- --' };
7
+ var counts = [
8
+ { key: 'running', label: 'running', n: s.running, tone: 'run' },
9
+ { key: 'blocked', label: 'blocked', n: s.blocked, tone: 'bad' },
10
+ { key: 'decisions', label: 'decisions', n: s.decisions, tone: 'warn' }
11
+ ];
12
+ counts.forEach(function (c) { c.zero = !c.n; });
13
+ return { kind: 'ready', ms: s.ms, counts: counts };
14
+ }
15
+
16
+ function pulseHtml(p) {
17
+ if (p.kind !== 'ready') {
18
+ return '<span class="pulse-note ' + p.kind + '">' + esc(p.text) + '</span>';
19
+ }
20
+ var out = '<span class="pulse-ms num">' + esc(p.ms) + '</span>';
21
+ p.counts.forEach(function (c) {
22
+ var tone = c.zero ? 'zero' : c.tone;
23
+ out += '<button type="button" class="pulse-item ' + tone + '" data-pulse="' + c.key + '">' +
24
+ (c.zero ? '' : '<span class="pdot"></span>') +
25
+ '<span class="num">' + c.n + '</span> ' + c.label + '</button>';
26
+ });
27
+ return out;
28
+ }
29
+
30
+ function pulseInput() {
31
+ var m = Studio.model;
32
+ var ms = null;
33
+ if (m) m.milestones.forEach(function (x) { if (x.id === m.activeMilestoneId) ms = x; });
34
+ // Same source as the Home decision queue, so the two counts agree.
35
+ var decisions = m && m.needsYou ? m.needsYou : [];
36
+ return {
37
+ loaded: !!(m && pipe.loaded.tasks),
38
+ offline: live.mode === 'offline',
39
+ ms: ms ? ms.id + ' ' + ms.done + '/' + ms.total : '',
40
+ running: m ? m.running.length : 0,
41
+ blocked: m ? m.stuck.length : 0,
42
+ decisions: decisions.length
43
+ };
44
+ }
45
+
46
+ var pulseEl = $('pulse');
47
+ function paintPulse() {
48
+ if (!pulseEl) return;
49
+ var html = pulseHtml(pulseParts(pulseInput()));
50
+ if (pulseEl.__html === html) return;
51
+ pulseEl.innerHTML = html;
52
+ pulseEl.__html = html;
53
+ }
54
+
55
+ // Studio.taskFilter ('running' or 'blocked') is read by the Tasks page when it opens.
56
+ if (pulseEl) pulseEl.addEventListener('click', function (e) {
57
+ var b = e.target.closest ? e.target.closest('[data-pulse]') : null;
58
+ if (!b) return;
59
+ var key = b.getAttribute('data-pulse');
60
+ if (key === 'decisions') { location.hash = '#home'; return; }
61
+ Studio.taskFilter = key;
62
+ location.hash = '#tasks';
63
+ });
64
+ Studio.onPaint(paintPulse);
65
+ paintPulse();
66
+
67
+ `;