@njeybe/nativ 2.5.1 → 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 (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +100 -5
  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 +55 -11
  7. package/dist/commands/notify.d.ts +2 -0
  8. package/dist/commands/notify.js +25 -0
  9. package/dist/commands/session.d.ts +22 -0
  10. package/dist/commands/session.js +106 -0
  11. package/dist/commands/task/task-add.js +1 -1
  12. package/dist/commands/task/task-common.js +6 -6
  13. package/dist/commands/task/task-escalate.js +8 -10
  14. package/dist/commands/task/task-lifecycle.d.ts +7 -1
  15. package/dist/commands/task/task-lifecycle.js +30 -5
  16. package/dist/commands/task/task-query.d.ts +2 -0
  17. package/dist/commands/task/task-query.js +27 -12
  18. package/dist/commands/task/task-reclaim.d.ts +20 -0
  19. package/dist/commands/task/task-reclaim.js +87 -0
  20. package/dist/commands/task.d.ts +2 -0
  21. package/dist/commands/task.js +1 -0
  22. package/dist/commands/triage.js +3 -3
  23. package/dist/commands/update.js +46 -0
  24. package/dist/commands/validate.js +1 -0
  25. package/dist/commands/verify.js +21 -5
  26. package/dist/core/design-rules.d.ts +23 -0
  27. package/dist/core/design-rules.js +115 -0
  28. package/dist/core/enforcement.js +18 -17
  29. package/dist/core/harness-integrity.js +26 -7
  30. package/dist/core/lock-manager.d.ts +6 -0
  31. package/dist/core/lock-manager.js +50 -41
  32. package/dist/core/notify.d.ts +38 -0
  33. package/dist/core/notify.js +133 -0
  34. package/dist/core/profiles.js +7 -16
  35. package/dist/core/session-memory.d.ts +42 -0
  36. package/dist/core/session-memory.js +121 -0
  37. package/dist/core/setup/pm-playbook.d.ts +5 -0
  38. package/dist/core/setup/pm-playbook.js +26 -0
  39. package/dist/core/setup/session-context.js +35 -2
  40. package/dist/core/setup/setup-planner.js +2 -2
  41. package/dist/core/setup/types.d.ts +2 -0
  42. package/dist/core/setup/types.js +5 -1
  43. package/dist/core/task-claims.d.ts +15 -0
  44. package/dist/core/task-claims.js +32 -0
  45. package/dist/core/tier1-liaison.d.ts +1 -0
  46. package/dist/core/tier1-liaison.js +25 -27
  47. package/dist/core/verifier/output-digest.d.ts +19 -0
  48. package/dist/core/verifier/output-digest.js +48 -0
  49. package/dist/db/data-engine.js +33 -32
  50. package/dist/db/introspector.js +61 -60
  51. package/dist/db/sqlite.d.ts +3 -0
  52. package/dist/db/sqlite.js +10 -0
  53. package/dist/governor/circuit-breaker.js +12 -0
  54. package/dist/governor/patch-applier.d.ts +1 -0
  55. package/dist/governor/patch-applier.js +3 -7
  56. package/dist/index.js +40 -6
  57. package/dist/mcp/server.d.ts +1 -1
  58. package/dist/mcp/server.js +53 -14
  59. package/dist/providers/claude-api.js +3 -0
  60. package/dist/providers/gemini.js +4 -1
  61. package/dist/providers/registry.d.ts +6 -2
  62. package/dist/providers/registry.js +28 -16
  63. package/dist/providers/types.d.ts +4 -1
  64. package/dist/runner/agent-supervisor.js +2 -2
  65. package/dist/runner/cli-config.js +1 -1
  66. package/dist/scanner/types.d.ts +5 -1
  67. package/dist/scanner/types.js +7 -0
  68. package/dist/server/routes/task-routes.d.ts +2 -0
  69. package/dist/server/routes/triage-routes.js +3 -18
  70. package/dist/server/studio-ui/pages/benchmarks/styles.js +8 -5
  71. package/dist/server/studio-ui/pages/database/browser.js +1 -1
  72. package/dist/server/studio-ui/pages/database/connection.js +1 -1
  73. package/dist/server/studio-ui/pages/database/data-shell.js +5 -5
  74. package/dist/server/studio-ui/pages/database/drift.js +1 -1
  75. package/dist/server/studio-ui/pages/database/explorer.js +2 -2
  76. package/dist/server/studio-ui/pages/database/styles.js +5 -5
  77. package/dist/server/studio-ui/pages/flow/styles.js +3 -3
  78. package/dist/server/studio-ui/pages/home/script.js +110 -91
  79. package/dist/server/studio-ui/pages/home/styles.js +33 -45
  80. package/dist/server/studio-ui/pages/tasks/script.js +150 -3
  81. package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
  82. package/dist/server/studio-ui/pages/team/script.js +49 -52
  83. package/dist/server/studio-ui/pages/team/styles.js +21 -20
  84. package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
  85. package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
  86. package/dist/server/studio-ui/shell/brand.d.ts +4 -0
  87. package/dist/server/studio-ui/shell/brand.js +16 -0
  88. package/dist/server/studio-ui/shell/console.js +61 -2
  89. package/dist/server/studio-ui/shell/core.js +8 -8
  90. package/dist/server/studio-ui/shell/detail-view.js +1 -1
  91. package/dist/server/studio-ui/shell/dispatch.js +18 -0
  92. package/dist/server/studio-ui/shell/events-tools.js +5 -1
  93. package/dist/server/studio-ui/shell/format.js +5 -6
  94. package/dist/server/studio-ui/shell/icons.js +23 -15
  95. package/dist/server/studio-ui/shell/markup-head.js +11 -6
  96. package/dist/server/studio-ui/shell/markup-tools.js +19 -10
  97. package/dist/server/studio-ui/shell/paint.js +3 -0
  98. package/dist/server/studio-ui/shell/pipeline-state.js +1 -1
  99. package/dist/server/studio-ui/shell/proposals.js +6 -2
  100. package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
  101. package/dist/server/studio-ui/shell/pulse.js +67 -0
  102. package/dist/server/studio-ui/shell/router.js +25 -0
  103. package/dist/server/studio-ui/shell/sidebar-view.js +6 -3
  104. package/dist/server/studio-ui/shell/toasts-format.js +4 -4
  105. package/dist/server/studio-ui/styles/console.js +33 -13
  106. package/dist/server/studio-ui/styles/dialogs.js +4 -4
  107. package/dist/server/studio-ui/styles/responsive.js +8 -0
  108. package/dist/server/studio-ui/styles/shell.js +24 -5
  109. package/dist/server/studio-ui/styles/strategist.js +10 -10
  110. package/dist/server/studio-ui/styles/tokens-base.js +23 -23
  111. package/dist/server/studio-ui.js +2 -0
  112. package/package.json +2 -2
  113. package/plugin/.claude-plugin/plugin.json +1 -1
  114. package/plugin/agents/architect.md +11 -12
  115. package/plugin/agents/explorer.md +1 -1
  116. package/plugin/agents/verifier.md +2 -2
  117. package/plugin/agents/worker.md +5 -7
  118. package/plugin/skills/nativ/SKILL.md +36 -49
  119. package/templates/AGENTS.md +36 -49
  120. package/templates/CLAUDE.md +1 -41
  121. package/templates/GEMINI.md +3 -2
  122. package/templates/claude-agents/architect.md +11 -12
  123. package/templates/claude-agents/explorer.md +1 -1
  124. package/templates/claude-agents/verifier.md +2 -2
  125. package/templates/claude-agents/worker.md +5 -7
  126. package/templates/design/taste/LICENSE +21 -0
  127. package/templates/design/taste/SOURCE.md +28 -0
  128. package/templates/design/taste/v1/DIGEST.md +57 -0
  129. package/templates/design/taste/v1/SKILL.md +226 -0
  130. package/templates/design/taste/v2/DIGEST.md +62 -0
  131. package/templates/design/taste/v2/SKILL.md +1206 -0
  132. package/templates/dot-ai/api_contracts.json +66 -66
  133. package/templates/dot-ai/context.md +38 -38
  134. package/templates/dot-ai/db_schema.json +39 -39
  135. package/templates/dot-ai/subagents/architect.md +6 -0
  136. package/templates/dot-ai/subagents/flutter-developer.md +2 -11
  137. package/templates/dot-ai/subagents/frontend.md +47 -44
  138. package/templates/dot-ai/ui_specs.md +15 -36
@@ -1,19 +1,22 @@
1
- // Home page client script: one pure render from the shared model.
1
+ // Home page client script: one pure render from the shared model, sections stacked top to bottom.
2
2
  export const homeScript = String.raw ` var esc = Studio.ui.esc;
3
- var RECENT_CAP = 6;
3
+ var UP_NEXT_CAP = 6;
4
+ var ACTIVITY_CAP = 8;
4
5
 
5
6
  function head() {
6
- return '<div class="page-head"><div><h1>Home</h1><p>What needs you, what is running, and what just finished.</p></div></div>';
7
+ return '<div class="page-head"><h1>Home</h1></div>';
7
8
  }
8
9
 
9
- function panel(title, right, body) {
10
- return '<section class="panel"><div class="panel-head"><h2>' + esc(title) + '</h2>' + right + '</div>' + body + '</section>';
10
+ function section(key, title, right, body) {
11
+ return '<section class="home-sec" data-sec="' + key + '"><div class="sec-head"><h2>' + esc(title) +
12
+ '</h2>' + (right || '') + '</div>' + body + '</section>';
11
13
  }
12
14
  function rightText(text) { return '<span class="right">' + esc(text) + '</span>'; }
13
15
 
14
- function actBtn(act, id, label, busy) {
16
+ function actBtn(act, id, label, busy, cls) {
15
17
  var on = busy === act.slice(5);
16
- return '<button type="button" class="btn" data-act="' + act + '" data-id="' + esc(id) + '"' + (busy ? ' disabled' : '') + '>' +
18
+ return '<button type="button" class="btn' + (cls ? ' ' + cls : '') + '" data-act="' + act +
19
+ '" data-id="' + esc(id) + '"' + (busy ? ' disabled' : '') + '>' +
17
20
  (on ? '<span class="spinner" aria-hidden="true"></span>' : '') + esc(label) + '</button>';
18
21
  }
19
22
 
@@ -24,127 +27,140 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
24
27
  return e.reason || e.summary || e.details || 'Waiting for your decision.';
25
28
  }
26
29
 
27
- function needItem(item) {
30
+ function needActions(item) {
28
31
  var t = item.task;
29
32
  var busy = Studio.busyAction ? Studio.busyAction(t.id) : null;
30
33
  var stuck = t.status === 'blocked';
31
- var out = '<button type="button" class="btn primary" data-task="' + esc(t.id) + '">' + (stuck ? 'See what failed' : 'Open task') + '</button>';
34
+ var out = '<button type="button" class="btn" data-task="' + esc(t.id) + '">' +
35
+ (stuck ? 'See what failed' : 'Open task') + '</button>';
32
36
  if (stuck) out += actBtn('home-start', t.id, 'Try again', busy);
33
- if (item.hasProposal) out += actBtn('home-proposal', t.id, 'Review proposal', busy);
34
- else if (item.escalation) out += actBtn('home-ask', t.id, 'Ask the architect', busy);
35
- return '<div class="need" data-need="' + esc(t.id) + '">' + Studio.ui.statusIcon('blocked') + '<div class="body">' +
36
- '<div><span class="mono faint">' + esc(t.id) + '</span> · ' + Studio.ui.agentChip(t.agent) + '</div>' +
37
- '<b style="font-weight:500">' + esc(t.title) + '</b><span class="why">' + esc(needReason(item)) + '</span>' +
38
- '<div class="row">' + out + '</div></div></div>';
37
+ if (item.hasProposal) out += actBtn('home-proposal', t.id, 'Review proposal', busy, 'primary');
38
+ else if (item.escalation) out += actBtn('home-ask', t.id, 'Ask the architect', busy, 'primary');
39
+ return out;
39
40
  }
40
41
 
41
- function needsPanel(model) {
42
- var n = model.needsYou;
43
- var body = n.length ? n.map(needItem).join('') :
44
- '<div class="calm">' + Studio.ui.statusIcon('completed') + 'Nothing needs you. No stuck tasks, questions or proposals.</div>';
45
- var right = n.length ? n.length + (n.length === 1 ? ' item' : ' items') : 'All clear';
46
- return panel('Needs you', rightText(right), '<div class="panel-body">' + body + '</div>');
42
+ function needItem(item) {
43
+ var t = item.task;
44
+ return '<div class="need" data-need="' + esc(t.id) + '"><div class="body">' +
45
+ '<div class="l1"><b>Human Decision Required</b><span class="mono">' + esc(t.id) + '</span></div>' +
46
+ '<span class="why">' + esc(needReason(item)) + '</span></div>' +
47
+ '<div class="acts">' + needActions(item) + '</div></div>';
47
48
  }
48
49
 
49
- function lastFinished(model) {
50
- var done = model.tasks.filter(function (t) { return t.status === 'completed'; });
51
- var timed = done.filter(function (t) { return t.endT; }).sort(function (a, b) { return b.endT - a.endT; });
52
- return timed[0] || done[done.length - 1] || null;
50
+ function decisionQueue(model) {
51
+ var n = model.needsYou;
52
+ if (!n.length) return '';
53
+ var count = n.length + (n.length === 1 ? ' item' : ' items');
54
+ return '<section class="home-sec decisions" data-sec="decisions"><div class="sec-head"><h2>Decisions</h2>' +
55
+ rightText(count) + '</div>' + n.map(needItem).join('') + '</section>';
53
56
  }
54
57
 
55
- function idleText(model) {
56
- var last = lastFinished(model);
57
- if (!last) return 'No agent is working. Last finished nothing yet.';
58
- var when = last.endT ? ' ' + esc(Studio.fmt.ago(last.endT)) : '';
59
- return 'No agent is working. Last finished <span class="mono">' + esc(last.id) + '</span>' + when + '.';
58
+ function isReady(model, t) {
59
+ if (t.status !== 'pending') return false;
60
+ return t.isAvailable || t.deps.every(function (d) {
61
+ var dep = model.byId[d];
62
+ return !dep || dep.status === 'completed';
63
+ });
60
64
  }
61
65
 
62
- function nowItem(t) {
63
- var timer = t.startT ? '<span class="timer" data-since="' + t.startT + '">' + Studio.fmt.dur(Studio.now() - t.startT) + '</span>' : '';
64
- return '<button type="button" class="now" data-task="' + esc(t.id) + '"><div class="l1">' + Studio.ui.statusIcon('in_progress') +
65
- Studio.ui.agentChip(t.agent) + timer + '</div><div class="title">' + esc(t.title) + '</div>' +
66
- (t.latestLog ? '<div class="log">' + esc(t.latestLog) + '</div>' : '') + '</button>';
66
+ function readyTasks(model) {
67
+ return model.tasks.filter(function (t) { return isReady(model, t); });
67
68
  }
68
69
 
69
- function nowPanel(model) {
70
- var r = model.running;
71
- var body = r.length ? r.map(nowItem).join('') : '<div class="calm">' + idleText(model) + '</div>';
72
- return panel('Right now', rightText(r.length ? r.length + ' working' : 'Idle'), '<div class="panel-body">' + body + '</div>');
70
+ function dispatchBtn(id, label, cls) {
71
+ return '<button type="button" class="btn' + (cls ? ' ' + cls : '') + '" data-act="home-dispatch" data-id="' +
72
+ esc(id) + '">' + esc(label) + '</button>';
73
73
  }
74
74
 
75
- function figure(k, value, total, sub) {
76
- return '<div><span class="k">' + esc(k) + '</span><span class="v">' + esc(value) +
77
- (total == null ? '' : '<small> / ' + esc(total) + '</small>') + '</span><span class="s">' + esc(sub) + '</span></div>';
75
+ function taskCells(t) {
76
+ return '<span class="mono id">' + esc(t.id) + '</span>' +
77
+ '<button type="button" class="t link" data-task="' + esc(t.id) + '">' + esc(t.title) + '</button>' +
78
+ '<span class="agent">' + esc(t.agent || '') + '</span>';
78
79
  }
79
80
 
80
- function spendNote(s) {
81
- var parts = [];
82
- if (s.cacheHitRate != null) parts.push((s.cacheHitRate * 100).toFixed(1) + '% cached');
83
- if (s.cacheSavingsUsd != null) parts.push('saved about ' + Studio.fmt.money(s.cacheSavingsUsd));
84
- return parts.length ? parts.join(', ') : 'no spend recorded';
81
+ function runRow(t) {
82
+ var since = t.startT ? ' data-since="' + t.startT + '">' + Studio.fmt.dur(Studio.now() - t.startT) : '>';
83
+ var row = '<div class="row run-row">' + taskCells(t) +
84
+ '<span class="state"><i class="dot run"></i>Running</span>' +
85
+ '<span class="mono num timer"' + since + '</span></div>';
86
+ var log = '<span class="mono log">' + esc(t.latestLog || '') + '</span>';
87
+ return row + '<div class="row sub-row">' + log + '<button type="button" class="btn" data-act="home-console"' +
88
+ ' data-id="' + esc(t.id) + '">Open console</button></div>';
85
89
  }
86
90
 
87
- function stripPanel(model) {
88
- var s = model.summary;
89
- var left = s.tasksLeft ? s.tasksLeft + ' left' : 'all done';
90
- var have = s.contractsPresent;
91
- var plan = have == null ? 'not checked' : (have >= s.contractsTotal ? 'all present and intact' : (s.contractsTotal - have) + ' missing');
92
- return '<section class="panel strip">' +
93
- figure('Milestones', s.milestonesDone, s.milestonesTotal, 'finished') +
94
- figure('Tasks', s.tasksDone, s.tasksTotal, left) +
95
- figure('Spend', Studio.fmt.money(s.spendUsd), null, spendNote(s)) +
96
- figure('Plan files', have == null ? '–' : have, s.contractsTotal, plan) + '</section>';
91
+ function nowRunning(model) {
92
+ var r = model.running;
93
+ if (r.length) return section('running', 'Now running', rightText(r.length + ' running'), r.map(runRow).join(''));
94
+ var next = readyTasks(model)[0];
95
+ var btn = next ? dispatchBtn(next.id, 'Dispatch ' + next.id, 'primary') : '';
96
+ return section('running', 'Now running', '', '<div class="row calm"><span>No agent is running.</span>' + btn + '</div>');
97
97
  }
98
98
 
99
- function recentTasks(model) {
100
- return model.tasks.filter(function (t) { return t.status === 'completed' && t.endT; })
101
- .sort(function (a, b) { return b.endT - a.endT; }).slice(0, RECENT_CAP);
99
+ function waitOn(model, t) {
100
+ var open = t.deps.filter(function (d) { var x = model.byId[d]; return x && x.status !== 'completed'; });
101
+ return open[0] || '';
102
102
  }
103
103
 
104
- function finishedRow(t) {
105
- return '<button type="button" class="row-btn" data-task="' + esc(t.id) + '">' + Studio.ui.statusIcon(t.status) +
106
- '<span class="mono faint hide-sm">' + esc(t.id) + '</span><span class="t">' + esc(t.title) + '</span>' +
107
- '<span class="hide-sm">' + Studio.ui.agentChip(t.agent) + '</span>' +
108
- '<span class="faint num row-right">' + Studio.ui.when(t.endT) + '</span></button>';
104
+ function nextRow(model, t) {
105
+ var end = isReady(model, t) ? dispatchBtn(t.id, 'Dispatch') :
106
+ '<span class="wait">waits <span class="mono">' + esc(waitOn(model, t)) + '</span></span>';
107
+ return '<div class="row next-row">' + taskCells(t) + end + '</div>';
109
108
  }
110
109
 
111
- function finishedPanel(model) {
112
- var list = recentTasks(model);
113
- var body = list.length ? '<div class="list">' + list.map(finishedRow).join('') + '</div>' :
114
- Studio.ui.empty('Nothing has finished yet.', '');
115
- var btn = '<button type="button" class="btn ghost right" data-go="tasks">Open Tasks</button>';
116
- return panel('Just finished', btn, body);
110
+ function upNext(model) {
111
+ var list = model.tasks.filter(function (t) { return t.status === 'pending'; });
112
+ var ready = list.filter(function (t) { return isReady(model, t); });
113
+ var waiting = list.filter(function (t) { return !isReady(model, t); });
114
+ var rows = ready.concat(waiting).slice(0, UP_NEXT_CAP);
115
+ var body = rows.length ? rows.map(function (t) { return nextRow(model, t); }).join('') :
116
+ '<div class="row calm">Nothing is waiting.</div>';
117
+ return section('next', 'Up next', '', body);
117
118
  }
118
119
 
119
- function msRow(m) {
120
+ function milestoneProgress(model) {
121
+ var m = model.milestones.filter(function (x) { return x.id === model.activeMilestoneId; })[0];
122
+ if (!m) return '';
120
123
  var pct = m.total ? Math.round((m.done / m.total) * 100) : 0;
121
- return '<button type="button" class="ms-row" data-ms="' + esc(m.id) + '"><span class="mono faint">' + esc(m.id) + '</span>' +
122
- '<span class="t" title="' + esc(m.name) + '">' + esc(m.short) + '</span>' +
123
- '<span class="bar"><i style="width:' + pct + '%"></i></span>' +
124
- '<span class="num faint" style="font-size:12px;text-align:right">' + m.done + '/' + m.total + '</span></button>';
124
+ var body = '<div class="row ms-row" data-ms="' + esc(m.id) + '"><span class="mono id">' + esc(m.id) + '</span>' +
125
+ '<span class="t">' + esc(m.name) + '</span><span class="mono num">' + m.done + ' of ' + m.total + '</span></div>' +
126
+ '<div class="progress"><i style="width:' + pct + '%"></i></div>';
127
+ return section('milestone', 'Milestone', '', body);
128
+ }
129
+
130
+ function activityEvents(model) {
131
+ var out = [];
132
+ model.tasks.forEach(function (t) {
133
+ if (t.endT) out.push({ at: t.endT, id: t.id, what: 'completed' });
134
+ if (t.startT) out.push({ at: t.startT, id: t.id, what: 'started' });
135
+ });
136
+ return out.sort(function (a, b) { return b.at - a.at; }).slice(0, ACTIVITY_CAP);
137
+ }
138
+
139
+ function activityRow(e) {
140
+ return '<div class="row act-row" data-event="' + esc(e.id) + '"><span class="mono num when">' +
141
+ Studio.ui.when(e.at) + '</span><span class="mono id">' + esc(e.id) + '</span><span>' + e.what + '</span></div>';
125
142
  }
126
143
 
127
- function milestonesPanel(model) {
128
- var list = model.milestones.slice().reverse();
129
- var body = list.length ? '<div class="list">' + list.map(msRow).join('') + '</div>' :
130
- Studio.ui.empty('No milestones yet.', 'The plan has no milestones.');
131
- return panel('Milestones', rightText('newest first'), body);
144
+ function recentActivity(model) {
145
+ var list = activityEvents(model);
146
+ var body = list.length ? list.map(activityRow).join('') : '<div class="row calm">Nothing has happened yet.</div>';
147
+ return section('activity', 'Recent activity', '', body);
132
148
  }
133
149
 
134
150
  function skeleton() {
135
- var bar = function (w) { return '<div class="skel" style="width:' + w + '"></div>'; };
136
- var block = '<div class="skel-block">' + bar('40%') + bar('100%') + bar('80%') + '</div>';
137
- return head() + '<div class="home-top">' + block + block + '</div>' +
138
- '<div class="skel-strip">' + block + block + block + block + '</div>' +
139
- '<div class="home-top">' + block + block + '</div>';
151
+ var row = '<div class="row skel-row"><div class="skel"></div></div>';
152
+ return '<div class="home">' + head() + '<section class="home-sec" aria-busy="true">' +
153
+ row + row + row + row + '</section></div>';
140
154
  }
141
155
 
142
156
  function render(ctx) {
143
157
  var model = ctx.model;
144
- return head() +
145
- '<div class="home-top">' + needsPanel(model) + nowPanel(model) + '</div>' +
146
- stripPanel(model) +
147
- '<div class="home-top">' + finishedPanel(model) + milestonesPanel(model) + '</div>';
158
+ if (!model.milestones.length) {
159
+ return '<div class="home">' + head() +
160
+ '<div class="row calm">No plan yet. Ask the architect to design one.</div></div>';
161
+ }
162
+ return '<div class="home">' + head() + decisionQueue(model) + nowRunning(model) + upNext(model) +
163
+ milestoneProgress(model) + recentActivity(model) + '</div>';
148
164
  }
149
165
 
150
166
  function forward(name) {
@@ -155,6 +171,9 @@ export const homeScript = String.raw ` var esc = Studio.ui.esc;
155
171
  deps: ['status', 'tasks', 'telemetry', 'runs', 'escalations'],
156
172
  render: render,
157
173
  skeleton: skeleton,
158
- actions: { 'home-start': forward('start'), 'home-proposal': forward('proposal'), 'home-ask': forward('ask') }
174
+ actions: {
175
+ 'home-start': forward('start'), 'home-proposal': forward('proposal'), 'home-ask': forward('ask'),
176
+ 'home-console': forward('logs'), 'home-dispatch': forward('dispatch')
177
+ }
159
178
  });
160
179
  `;
@@ -1,48 +1,36 @@
1
- // Home page styles, all scoped under #panel-home.
2
- export const homeCss = String.raw `#panel-home .page-head { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
3
- #panel-home .page-head h1 { font-size: 22px; font-weight: 600; margin: 0; letter-spacing: -.01em; text-wrap: balance; }
4
- #panel-home .page-head p { margin: 4px 0 0; color: var(--fg-2); }
5
- #panel-home .panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; min-width: 0; }
6
- #panel-home .panel-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
7
- #panel-home .panel-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
8
- #panel-home .panel-head .right { margin-left: auto; font-size: 12.5px; color: var(--fg-3); }
9
- #panel-home .panel-body { padding: 14px 16px; }
10
- #panel-home .home-top { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 16px; }
11
- #panel-home .need { display: flex; gap: 12px; padding: 12px; border-radius: 10px; background: var(--bad-soft); }
12
- #panel-home .need + .need { margin-top: 8px; }
13
- #panel-home .need .body { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
1
+ // Home page styles, all scoped under #panel-home. Ink: flat sections, one raised decision surface.
2
+ export const homeCss = String.raw `#panel-home .home { max-width: 960px; display: flex; flex-direction: column; }
3
+ #panel-home .page-head h1 { font-size: 22px; font-weight: 600; margin: 0 0 12px; letter-spacing: -.01em; }
4
+ #panel-home .home-sec { border-top: 1px solid var(--line); padding: 12px 0; min-width: 0; }
5
+ #panel-home .sec-head { display: flex; align-items: center; gap: 8px; min-height: 28px; }
6
+ #panel-home .sec-head h2 { font-size: 13px; font-weight: 600; margin: 0; color: var(--fg-2); }
7
+ #panel-home .sec-head .right { margin-left: auto; font-size: 12px; color: var(--fg-3); }
8
+ #panel-home .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
9
+ #panel-home .row { display: flex; align-items: center; gap: 12px; min-height: 36px; border-bottom: 1px solid var(--line); }
10
+ #panel-home .row:last-child { border-bottom: 0; }
11
+ #panel-home .row .id { width: 72px; flex: none; color: var(--fg-2); font-size: 12px; }
12
+ #panel-home .row .t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
13
+ #panel-home .link { background: none; border: 0; padding: 0; text-align: left; color: var(--fg); cursor: pointer; font: inherit; }
14
+ #panel-home .agent { width: 96px; flex: none; color: var(--fg-2); font-size: 12.5px; }
15
+ #panel-home .state { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
16
+ #panel-home .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--fg-3); }
17
+ #panel-home .dot.run { background: var(--run); }
18
+ #panel-home .timer { width: 64px; text-align: right; font-size: 12px; }
19
+ #panel-home .sub-row .log { flex: 1; min-width: 0; font-size: 12px; color: var(--fg-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
20
+ #panel-home .wait { color: var(--fg-3); font-size: 12.5px; }
21
+ #panel-home .calm { color: var(--fg-2); justify-content: space-between; }
22
+ #panel-home .decisions { background: var(--surface); border: 0; border-left: 3px solid var(--warn); border-radius: 6px; box-shadow: var(--shadow); padding: 8px 16px; margin-bottom: 12px; }
23
+ #panel-home .need { display: flex; align-items: center; gap: 12px; min-height: 36px; padding: 8px 0; border-top: 1px solid var(--line); flex-wrap: wrap; }
24
+ #panel-home .need .body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
25
+ #panel-home .need .l1 { display: flex; gap: 8px; align-items: baseline; }
14
26
  #panel-home .need .why { font-size: 13px; color: var(--fg-2); overflow-wrap: anywhere; }
15
- #panel-home .need .row { display: flex; gap: 8px; flex-wrap: wrap; }
16
- #panel-home .calm { display: flex; align-items: center; gap: 10px; color: var(--fg-2); padding: 6px 0; }
17
- #panel-home .calm .st { color: var(--ok); }
18
- #panel-home .now { display: flex; flex-direction: column; gap: 6px; width: 100%; text-align: left; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); cursor: pointer; }
19
- #panel-home .now:hover { background: var(--surface-2); }
20
- #panel-home .now + .now { margin-top: 8px; }
21
- #panel-home .now .l1 { display: flex; align-items: center; gap: 8px; }
22
- #panel-home .now .timer { margin-left: auto; font: 12px var(--mono); color: var(--run); }
23
- #panel-home .now .log { font: 11.5px var(--mono); color: var(--fg-2); background: var(--surface-2); padding: 6px 8px; border-radius: 6px; overflow-wrap: anywhere; }
24
- #panel-home .strip { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
25
- #panel-home .strip > div { padding: 14px 16px; display: flex; flex-direction: column; gap: 2px; }
26
- #panel-home .strip > div + div { border-left: 1px solid var(--line); }
27
- #panel-home .strip .k { font-size: 12px; color: var(--fg-3); }
28
- #panel-home .strip .v { font-size: 22px; font-weight: 600; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
29
- #panel-home .strip .v small { font-size: 13px; font-weight: 400; color: var(--fg-3); }
30
- #panel-home .strip .s { font-size: 12px; color: var(--fg-2); }
31
- #panel-home .list .row-btn:first-child, #panel-home .list .ms-row:first-child { border-top: 0; }
32
- #panel-home .ms-row { display: grid; grid-template-columns: 34px minmax(0, 1fr) 120px 64px; 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; }
33
- #panel-home .ms-row:hover { background: var(--surface-2); }
34
- #panel-home .ms-row .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
35
- #panel-home .bar { height: 5px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
36
- #panel-home .bar i { display: block; height: 100%; background: var(--ok); border-radius: 3px; }
37
- #panel-home .row-right { text-align: right; font-size: 12px; }
38
- #panel-home .skel-strip { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
39
- @media (max-width: 900px) {
40
- #panel-home .home-top { grid-template-columns: minmax(0, 1fr); }
41
- #panel-home .strip, #panel-home .skel-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
42
- #panel-home .strip > div:nth-child(3) { border-left: 0; }
43
- }
44
- @media (max-width: 760px) {
45
- #panel-home .ms-row { grid-template-columns: 34px minmax(0, 1fr) 64px; }
46
- #panel-home .ms-row .bar { display: none; }
27
+ #panel-home .need .acts { display: flex; gap: 8px; flex-wrap: wrap; }
28
+ #panel-home .progress { height: 4px; border-bottom: 1px solid var(--line); }
29
+ #panel-home .progress i { display: block; height: 4px; background: var(--fg); border-radius: 2px; }
30
+ #panel-home .act-row .when { width: 72px; flex: none; font-size: 12px; color: var(--fg-3); }
31
+ #panel-home .skel-row .skel { height: 12px; width: 100%; border-radius: 6px; background: var(--surface-2); }
32
+ @media (max-width: 1024px) {
33
+ #panel-home .home { max-width: none; }
34
+ #panel-home .agent { display: none; }
47
35
  }
48
36
  `;
@@ -1,7 +1,132 @@
1
- // Tasks page: one milestone as four columns, then the other milestones as a history list.
1
+ // Tasks page: a grouped list by default, or one milestone as four columns (Board).
2
2
  export const tasksScript = String.raw ` var ui = Studio.ui;
3
3
  var CAP = 5;
4
- var view = { lastMs: null, showAll: false, open: {} };
4
+ var view = { lastMs: null, showAll: false, open: {}, mode: null, filter: 'all', groups: {} };
5
+ var KEY = 'nativ-studio:tasks-view';
6
+ var FILTERS = [['all', 'All'], ['running', 'Running'], ['ready', 'Ready'], ['blocked', 'Blocked'],
7
+ ['done', 'Done']];
8
+ var DOT = { completed: 'ok', in_progress: 'run', blocked: 'bad', ready: 'fg', waiting: 'faint' };
9
+ var LABEL = { completed: 'Done', in_progress: 'Running', blocked: 'Blocked', ready: 'Ready',
10
+ waiting: 'Waiting' };
11
+
12
+ function readMode() {
13
+ try { return localStorage.getItem(KEY) === 'board' ? 'board' : 'list'; } catch (e) { return 'list'; }
14
+ }
15
+ function saveMode(mode) {
16
+ try { localStorage.setItem(KEY, mode); } catch (e) { /* private mode */ }
17
+ }
18
+
19
+ // Same notion of ready as Home and the sidebar: pending and every dependency done.
20
+ function isReady(model, t) {
21
+ if (t.status !== 'pending') return false;
22
+ return t.isAvailable || t.deps.every(function (d) {
23
+ var dep = model.byId && model.byId[d];
24
+ return !dep || dep.status === 'completed';
25
+ });
26
+ }
27
+ function kindOf(model, t) {
28
+ if (t.status === 'pending') return isReady(model, t) ? 'ready' : 'waiting';
29
+ return t.status;
30
+ }
31
+ function matches(model, t, f) {
32
+ if (f === 'all') return true;
33
+ if (f === 'running') return t.status === 'in_progress';
34
+ if (f === 'blocked') return t.status === 'blocked';
35
+ if (f === 'done') return t.status === 'completed';
36
+ return isReady(model, t);
37
+ }
38
+
39
+ function toolbar(model) {
40
+ var chips = FILTERS.map(function (f) {
41
+ var on = view.filter === f[0];
42
+ var n = model.tasks.filter(function (t) { return matches(model, t, f[0]); }).length;
43
+ return '<button type="button" class="tf' + (on ? ' on' : '') + '" data-act="tasks-filter" data-filter="' +
44
+ f[0] + '" aria-pressed="' + on + '">' + f[1] + ' <span class="num">' + n + '</span></button>';
45
+ }).join('');
46
+ var seg = ['list', 'board'].map(function (m) {
47
+ var on = view.mode === m;
48
+ return '<button type="button" class="seg' + (on ? ' on' : '') + '" data-act="tasks-view" data-mode="' +
49
+ m + '" aria-pressed="' + on + '">' + (m === 'list' ? 'List' : 'Board') + '</button>';
50
+ }).join('');
51
+ return '<div class="tbar"><div class="tfilters" role="group" aria-label="Filter tasks">' + chips +
52
+ '</div><div class="tseg" role="group" aria-label="View">' + seg + '</div></div>';
53
+ }
54
+
55
+ function timeOf(t, fmt) {
56
+ if (t.status === 'in_progress' && t.startT) return fmt.dur(Studio.now() - t.startT);
57
+ return t.durationMs ? fmt.dur(t.durationMs) : '';
58
+ }
59
+
60
+ // Rows use data-tid, not data-task, so the nested Dispatch button gets its own click.
61
+ function listRow(model, t, fmt) {
62
+ var k = kindOf(model, t);
63
+ var id = ui.esc(t.id);
64
+ var go = k === 'ready' ? '<button type="button" class="tdispatch" data-act="tasks-dispatch" data-tid="' +
65
+ id + '">Dispatch</button>' : '';
66
+ return '<div class="trow" role="row" tabindex="0" data-act="tasks-open" data-tid="' + id + '">' +
67
+ '<span class="c-id mono" role="cell">' + id + '</span>' +
68
+ '<span class="c-title" role="cell" title="' + ui.esc(t.title) + '">' + ui.esc(t.title) + '</span>' +
69
+ '<span class="c-agent" role="cell">' + ui.esc(t.agent || '') + '</span>' +
70
+ '<span class="c-status st-' + DOT[k] + '" role="cell"><i class="dot"></i>' + LABEL[k] + go + '</span>' +
71
+ '<span class="c-time mono" role="cell">' + ui.esc(timeOf(t, fmt)) + '</span></div>';
72
+ }
73
+
74
+ function isOpen(m) {
75
+ if (view.groups[m.id] != null) return view.groups[m.id];
76
+ return !(m.total > 0 && m.done === m.total);
77
+ }
78
+
79
+ var THEAD = '<div class="trow thead" role="row"><span role="columnheader">Id</span>' +
80
+ '<span role="columnheader">Task</span><span class="c-agent" role="columnheader">Agent</span>' +
81
+ '<span role="columnheader">Status</span><span class="c-time" role="columnheader">Time</span></div>';
82
+
83
+ function group(model, m, rows, fmt) {
84
+ var open = isOpen(m);
85
+ var body = open ? THEAD + rows.map(function (t) { return listRow(model, t, fmt); }).join('') : '';
86
+ return '<section class="tgroup" role="table" aria-label="' + ui.esc(m.name) + '">' +
87
+ '<button type="button" class="tg-head" data-act="tasks-group" data-group="' + ui.esc(m.id) +
88
+ '" aria-expanded="' + open + '"><span class="chev">' + ui.icon('chev', 2) + '</span>' +
89
+ '<span class="mono faint">' + ui.esc(m.id) + '</span><span class="t">' + ui.esc(m.name) + '</span>' +
90
+ '<span class="num faint">' + m.done + ' of ' + m.total + '</span></button>' + body + '</section>';
91
+ }
92
+
93
+ function list(model, fmt) {
94
+ var html = model.milestones.map(function (m) {
95
+ var rows = m.tasks.filter(function (t) { return matches(model, t, view.filter); });
96
+ return rows.length ? group(model, m, rows, fmt) : '';
97
+ }).join('');
98
+ if (html) return '<div class="tlist">' + html + '</div>';
99
+ return '<div class="tempty"><p>No tasks match this filter.</p>' +
100
+ '<button type="button" class="btn" data-act="tasks-clear">Clear filter</button></div>';
101
+ }
102
+
103
+ function skeleton() {
104
+ var row = '<div class="trow tskel"><span></span><span></span><span></span><span></span><span></span></div>';
105
+ return '<div class="panel-head"><div class="panel-title"><h1>Tasks</h1></div></div>' +
106
+ '<div class="tlist">' + row + row + row + row + row + '</div>';
107
+ }
108
+
109
+ // The pulse strip leaves a filter on Studio.taskFilter; use it once.
110
+ function takeFilter() {
111
+ var f = Studio.taskFilter;
112
+ if (f && FILTERS.some(function (x) { return x[0] === f; })) view.filter = f;
113
+ Studio.taskFilter = null;
114
+ }
115
+
116
+ function onlyFiltered(model, m) {
117
+ var copy = Object.assign({}, m);
118
+ copy.tasks = m.tasks.filter(function (t) { return matches(model, t, view.filter); });
119
+ return copy;
120
+ }
121
+
122
+ if (typeof document !== 'undefined' && document.addEventListener) {
123
+ document.addEventListener('keydown', function (e) {
124
+ var el = e.target;
125
+ if (e.key !== 'Enter' || !el || !el.matches || !el.matches('.trow[data-tid]')) return;
126
+ e.preventDefault();
127
+ Studio.taskAction('logs', el.getAttribute('data-tid'));
128
+ });
129
+ }
5
130
 
6
131
  function head(m) {
7
132
  var lead = m ? ui.esc(m.name) : 'The work of one milestone, by state.';
@@ -76,15 +201,37 @@ export const tasksScript = String.raw ` var ui = Studio.ui;
76
201
 
77
202
  Studio.registerPage('tasks', {
78
203
  deps: ['tasks', 'runs', 'escalations'],
204
+ skeleton: skeleton,
79
205
  render: function (ctx) {
80
206
  var model = ctx.model;
207
+ if (!view.mode) view.mode = ctx.state.tasksView || readMode();
208
+ takeFilter();
209
+ if (!model.milestones.length) {
210
+ return head(null) + '<div class="tempty"><p>No plan yet. Ask the architect to design one.</p></div>';
211
+ }
212
+ if (view.mode === 'list') return head(null) + toolbar(model) + list(model, ctx.fmt);
81
213
  var wanted = ctx.state.milestoneId || model.activeMilestoneId;
82
214
  var m = model.milestones.filter(function (x) { return x.id === wanted; })[0];
83
215
  if (!m) return head(null) + ui.empty('No milestones yet', 'Add tasks to the plan to see them here.');
84
216
  if (view.lastMs !== m.id) { view.lastMs = m.id; view.showAll = false; }
85
- return head(m) + board(m, ctx.fmt) + others(model, m, ctx.fmt);
217
+ return head(m) + toolbar(model) + board(onlyFiltered(model, m), ctx.fmt) + others(model, m, ctx.fmt);
86
218
  },
87
219
  actions: {
220
+ 'tasks-view': function (el) {
221
+ view.mode = el.getAttribute('data-mode');
222
+ saveMode(view.mode);
223
+ Studio.repaint('tasks');
224
+ },
225
+ 'tasks-filter': function (el) { view.filter = el.getAttribute('data-filter'); Studio.repaint('tasks'); },
226
+ 'tasks-clear': function () { view.filter = 'all'; Studio.repaint('tasks'); },
227
+ 'tasks-open': function (el) { Studio.taskAction('logs', el.getAttribute('data-tid')); },
228
+ 'tasks-dispatch': function (el) { Studio.taskAction('dispatch', el.getAttribute('data-tid')); },
229
+ 'tasks-group': function (el, ctx) {
230
+ var id = el.getAttribute('data-group');
231
+ var m = ctx.model.milestones.filter(function (x) { return x.id === id; })[0];
232
+ view.groups[id] = !(m && isOpen(m));
233
+ Studio.repaint('tasks');
234
+ },
88
235
  'tasks-more': function () { view.showAll = !view.showAll; Studio.repaint('tasks'); },
89
236
  'tasks-hist': function (el) {
90
237
  var id = el.getAttribute('data-ms-id');