@njeybe/nativ 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/README.md +17 -0
- package/dist/server/studio-ui/document.d.ts +3 -0
- package/dist/server/studio-ui/document.js +22 -0
- package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
- package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/browser.js +326 -0
- package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/connection.js +158 -0
- package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
- package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/drift.js +301 -0
- package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/events.js +231 -0
- package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/explorer.js +183 -0
- package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/index.js +28 -0
- package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/markup.js +32 -0
- package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/state.js +57 -0
- package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/styles.js +118 -0
- package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/flow/index.js +11 -0
- package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/layout.js +105 -0
- package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
- package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/script.js +61 -0
- package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/styles.js +59 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
- package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/home/index.js +11 -0
- package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/script.js +160 -0
- package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/styles.js +48 -0
- package/dist/server/studio-ui/pages/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/index.js +17 -0
- package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/tasks/index.js +11 -0
- package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/script.js +96 -0
- package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
- package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/team/index.js +11 -0
- package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/script.js +112 -0
- package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/styles.js +22 -0
- package/dist/server/studio-ui/pages/types.d.ts +13 -0
- package/dist/server/studio-ui/pages/types.js +1 -0
- package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
- package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
- package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
- package/dist/server/studio-ui/shell/actions.d.ts +1 -0
- package/dist/server/studio-ui/shell/actions.js +49 -0
- package/dist/server/studio-ui/shell/boot.d.ts +2 -0
- package/dist/server/studio-ui/shell/boot.js +13 -0
- package/dist/server/studio-ui/shell/console.d.ts +1 -0
- package/dist/server/studio-ui/shell/console.js +194 -0
- package/dist/server/studio-ui/shell/core.d.ts +1 -0
- package/dist/server/studio-ui/shell/core.js +50 -0
- package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail-view.js +80 -0
- package/dist/server/studio-ui/shell/detail.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail.js +83 -0
- package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
- package/dist/server/studio-ui/shell/dispatch.js +156 -0
- package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/events-tools.js +94 -0
- package/dist/server/studio-ui/shell/format.d.ts +1 -0
- package/dist/server/studio-ui/shell/format.js +151 -0
- package/dist/server/studio-ui/shell/icons.d.ts +2 -0
- package/dist/server/studio-ui/shell/icons.js +20 -0
- package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-head.js +43 -0
- package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
- package/dist/server/studio-ui/shell/markup-panels.js +7 -0
- package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-tools.js +222 -0
- package/dist/server/studio-ui/shell/model.d.ts +1 -0
- package/dist/server/studio-ui/shell/model.js +192 -0
- package/dist/server/studio-ui/shell/paint.d.ts +1 -0
- package/dist/server/studio-ui/shell/paint.js +154 -0
- package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette-view.js +60 -0
- package/dist/server/studio-ui/shell/palette.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette.js +80 -0
- package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
- package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
- package/dist/server/studio-ui/shell/proposals.js +197 -0
- package/dist/server/studio-ui/shell/pure.d.ts +3 -0
- package/dist/server/studio-ui/shell/pure.js +9 -0
- package/dist/server/studio-ui/shell/router.d.ts +1 -0
- package/dist/server/studio-ui/shell/router.js +95 -0
- package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
- package/dist/server/studio-ui/shell/sync.d.ts +1 -0
- package/dist/server/studio-ui/shell/sync.js +120 -0
- package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
- package/dist/server/studio-ui/shell/toasts-format.js +89 -0
- package/dist/server/studio-ui/shell/triage.d.ts +1 -0
- package/dist/server/studio-ui/shell/triage.js +210 -0
- package/dist/server/studio-ui/styles/console.d.ts +1 -0
- package/dist/server/studio-ui/styles/console.js +83 -0
- package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
- package/dist/server/studio-ui/styles/dialogs.js +118 -0
- package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
- package/dist/server/studio-ui/styles/responsive.js +28 -0
- package/dist/server/studio-ui/styles/shell.d.ts +1 -0
- package/dist/server/studio-ui/styles/shell.js +117 -0
- package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
- package/dist/server/studio-ui/styles/strategist.js +77 -0
- package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
- package/dist/server/studio-ui/styles/tokens-base.js +131 -0
- package/dist/server/studio-ui.d.ts +1 -10
- package/dist/server/studio-ui.js +63 -5314
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/architect.md +9 -5
- package/templates/GEMINI.md +12 -8
- package/templates/claude-agents/architect.md +9 -5
- package/templates/dot-ai/subagents/flutter-developer.md +10 -0
- package/templates/dot-ai/subagents/frontend.md +10 -0
- package/templates/dot-ai/ui_specs.md +36 -6
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Pure task detail panel markup: sections and actions depend on the task status.
|
|
2
|
+
export const detailViewScript = String.raw ` // ─── Task detail panel (pure) ───────────────────────────────────────────────
|
|
3
|
+
var BUSY_LABEL = { 'task-start': 'Starting…', 'task-dispatch': 'Dispatching…', 'task-abort': 'Stopping…', 'task-ask': 'Asking…' };
|
|
4
|
+
|
|
5
|
+
function dBtn(act, label, opts) {
|
|
6
|
+
opts = opts || {};
|
|
7
|
+
var busy = opts.busy === act;
|
|
8
|
+
var off = opts.disabled || (opts.busy && !busy);
|
|
9
|
+
return '<button type="button" class="btn' + (opts.primary ? ' primary' : '') + (opts.danger ? ' danger' : '') + '" data-act="' + act + '"' +
|
|
10
|
+
(off || busy ? ' disabled' : '') + (opts.title ? ' title="' + esc(opts.title) + '"' : '') + '>' +
|
|
11
|
+
(busy ? '<span class="spinner" aria-hidden="true"></span>' + BUSY_LABEL[act] : esc(label)) + '</button>';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function depChip(model, id) {
|
|
15
|
+
var x = model.byId[id];
|
|
16
|
+
if (!x) return '<span class="chip mono">' + esc(id) + '</span>';
|
|
17
|
+
return '<button type="button" class="chip" data-task="' + esc(id) + '">' + ui.statusIcon(x.status) +
|
|
18
|
+
'<span class="mono">' + esc(id) + '</span></button>';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function detailActions(t, env) {
|
|
22
|
+
var b = env.busy ? 'task-' + env.busy : null;
|
|
23
|
+
if (t.status === 'blocked') {
|
|
24
|
+
var out = dBtn('task-output', 'Show check output', { primary: true, busy: b }) + dBtn('task-start', 'Try again', { busy: b });
|
|
25
|
+
if (t.hasProposal) return out + dBtn('task-proposal', 'Review proposal', { busy: b });
|
|
26
|
+
if (t.escalation) out += dBtn('task-ask', 'Ask the architect', { busy: env.triaging ? 'task-ask' : b });
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
if (t.status === 'in_progress') {
|
|
30
|
+
var live = t.run && isRunActive(t.run);
|
|
31
|
+
return dBtn('task-logs', 'Open live logs', { busy: b }) +
|
|
32
|
+
dBtn('task-abort', 'Stop', { danger: true, busy: b, disabled: !live, title: live ? '' : 'No runner is attached to this task' });
|
|
33
|
+
}
|
|
34
|
+
if (t.status === 'pending' && !t.waitingOn.length) {
|
|
35
|
+
return dBtn('task-start', 'Start', { primary: true, busy: b }) + dBtn('task-dispatch', 'Dispatch', { busy: b });
|
|
36
|
+
}
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detailTook(t) {
|
|
41
|
+
if (t.status === 'in_progress' && t.startT) {
|
|
42
|
+
return '<span data-since="' + t.startT + '">' + fmt.dur(Studio.now() - t.startT) + '</span> so far';
|
|
43
|
+
}
|
|
44
|
+
var check = t.checkMs ? ' <span class="faint">(check ' + fmt.dur(t.checkMs) + ')</span>' : '';
|
|
45
|
+
return fmt.dur(t.durationMs) + check;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detailFacts(t, model) {
|
|
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>' : '–';
|
|
51
|
+
return '<dl class="kv"><dt>Milestone</dt><dd>' + ms + '</dd><dt>Agent</dt><dd>' + ui.agentChip(t.agent) + '</dd>' +
|
|
52
|
+
'<dt>Started</dt><dd class="num">' + esc(fmt.full(t.startT)) + '</dd><dt>Took</dt><dd class="num">' + detailTook(t) + '</dd>' +
|
|
53
|
+
'<dt>Est. cost</dt><dd class="num">' + esc(fmt.money(t.costUsd)) + '</dd></dl>';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function chipList(label, html) { return '<div><div class="dlabel">' + label + '</div><div class="chips">' + html + '</div></div>'; }
|
|
57
|
+
|
|
58
|
+
/** env = { busy: running action key or null, triaging: bool }. Returns the drawer content. */
|
|
59
|
+
Studio.detailHtml = function (id, env) {
|
|
60
|
+
env = env || {};
|
|
61
|
+
var model = Studio.model, t = model && model.byId[id];
|
|
62
|
+
if (!t) return '';
|
|
63
|
+
var reason = t.status === 'blocked'
|
|
64
|
+
? '<div class="reason">' + esc(t.reason || 'Paused after ' + (t.attempt || t.maxAttempts) + ' of ' + t.maxAttempts + ' attempts.') + '</div>' : '';
|
|
65
|
+
var step = t.status === 'in_progress'
|
|
66
|
+
? '<div><div class="dlabel">Latest step</div><div class="code wrap">' + esc(t.latestLog || 'No output yet.') + '</div></div>' : '';
|
|
67
|
+
var actions = detailActions(t, env);
|
|
68
|
+
var files = t.files.map(function (f) { return '<span class="chip mono">' + esc(f) + '</span>'; }).join('') || '<span class="faint">None listed.</span>';
|
|
69
|
+
return '<div class="drawer-head">' + ui.statusIcon(t.status) + '<span class="mono faint">' + esc(t.id) + '</span>' +
|
|
70
|
+
ui.pill(t.status) + '<button type="button" class="btn ghost close" data-act="close-detail" aria-label="Close">' + ui.icon('x') + '</button></div>' +
|
|
71
|
+
'<div class="drawer-body"><h3>' + esc(t.title) + '</h3>' + reason + (actions ? '<div class="chips">' + actions + '</div>' : '') + step +
|
|
72
|
+
detailFacts(t, model) +
|
|
73
|
+
chipList('Needs first', t.deps.length ? t.deps.map(function (d) { return depChip(model, d); }).join('') : '<span class="faint">Nothing. It can start right away.</span>') +
|
|
74
|
+
chipList('Unblocks', t.children.length ? t.children.map(function (d) { return depChip(model, d); }).join('') : '<span class="faint">No later task depends on this.</span>') +
|
|
75
|
+
chipList('Files it may change', files) +
|
|
76
|
+
'<div><div class="dlabel">How it is checked</div><div class="code">' + esc(t.verify || 'No check command.') + '</div></div>' +
|
|
77
|
+
'<div class="chips"><button type="button" class="btn" data-act="show-flow">' + ui.icon('flow') + 'Show in Flow</button></div></div>';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const detailScript: string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Task detail panel: opens from any data-task, closes with Esc, returns focus to its opener.
|
|
2
|
+
export const detailScript = String.raw ` // ─── Task detail panel ─────────────────────────────────────────────────────
|
|
3
|
+
var detail = { opener: null, html: '' };
|
|
4
|
+
|
|
5
|
+
function detailEnv(id) {
|
|
6
|
+
var esc1 = escalationFor(id);
|
|
7
|
+
return { busy: Studio.busyAction(id), triaging: !!(esc1 && pipe.triageRunning[esc1.id]) };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function showDetail(id) {
|
|
11
|
+
var drawer = $('task-drawer');
|
|
12
|
+
var html = Studio.detailHtml(id, detailEnv(id));
|
|
13
|
+
var memo = focusMemo(drawer);
|
|
14
|
+
if (html !== detail.html) { drawer.innerHTML = html; detail.html = html; restoreFocus(drawer, memo); }
|
|
15
|
+
drawer.hidden = false;
|
|
16
|
+
$('scrim').hidden = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function openDetail(id) {
|
|
20
|
+
if (!Studio.model || !Studio.model.byId[id]) return;
|
|
21
|
+
if (!Studio.state.detailTaskId) detail.opener = document.activeElement;
|
|
22
|
+
Studio.state.detailTaskId = id;
|
|
23
|
+
showDetail(id);
|
|
24
|
+
if (!$('task-drawer').contains(document.activeElement)) {
|
|
25
|
+
var close = $('task-drawer').querySelector('[data-act="close-detail"]');
|
|
26
|
+
if (close) close.focus();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
Studio.openTask = openDetail;
|
|
30
|
+
|
|
31
|
+
/** Re-renders the open panel after a model change; closes it if the task disappeared. */
|
|
32
|
+
function refreshDetail() {
|
|
33
|
+
var id = Studio.state.detailTaskId;
|
|
34
|
+
if (!id) return;
|
|
35
|
+
if (!Studio.model || !Studio.model.byId[id]) closeDetail(false);
|
|
36
|
+
else showDetail(id);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function closeDetail(quiet) {
|
|
40
|
+
var S = Studio.state;
|
|
41
|
+
var wasOpen = !!S.detailTaskId;
|
|
42
|
+
S.detailTaskId = null;
|
|
43
|
+
$('task-drawer').hidden = true;
|
|
44
|
+
$('scrim').hidden = true;
|
|
45
|
+
$('task-drawer').innerHTML = '';
|
|
46
|
+
detail.html = '';
|
|
47
|
+
var hadFocus = !!S.focusTaskId;
|
|
48
|
+
S.focusTaskId = null;
|
|
49
|
+
if (quiet) return;
|
|
50
|
+
if (hadFocus) repaintFor(null);
|
|
51
|
+
var opener = detail.opener;
|
|
52
|
+
detail.opener = null;
|
|
53
|
+
if (wasOpen && opener && document.body.contains(opener) && opener.focus) opener.focus();
|
|
54
|
+
}
|
|
55
|
+
Studio.closeDetail = function () { closeDetail(false); };
|
|
56
|
+
|
|
57
|
+
$('scrim').addEventListener('click', function () { closeDetail(false); });
|
|
58
|
+
$('task-drawer').addEventListener('click', function (e) {
|
|
59
|
+
var t = e.target;
|
|
60
|
+
var task = t.closest('[data-task]');
|
|
61
|
+
if (task) { openDetail(task.getAttribute('data-task')); return; }
|
|
62
|
+
var ms = t.closest('[data-ms]');
|
|
63
|
+
if (ms) { Studio.go('tasks', { milestoneId: ms.getAttribute('data-ms') }); return; }
|
|
64
|
+
var act = t.closest('[data-act]');
|
|
65
|
+
if (!act || act.disabled) return;
|
|
66
|
+
var name = act.getAttribute('data-act');
|
|
67
|
+
var id = Studio.state.detailTaskId;
|
|
68
|
+
if (name === 'close-detail') closeDetail(false);
|
|
69
|
+
else if (name === 'show-flow') Studio.taskAction('flow', id);
|
|
70
|
+
else if (name.indexOf('task-') === 0) Studio.taskAction(name.slice(5), id);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Keep Tab inside the panel while it is open.
|
|
74
|
+
$('task-drawer').addEventListener('keydown', function (e) {
|
|
75
|
+
if (e.key !== 'Tab') return;
|
|
76
|
+
var items = $('task-drawer').querySelectorAll('button:not([disabled]), [href], select, input');
|
|
77
|
+
if (!items.length) return;
|
|
78
|
+
var first = items[0], last = items[items.length - 1];
|
|
79
|
+
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
|
|
80
|
+
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const dispatchScript: string;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Dispatch requests and the dispatch modal.
|
|
2
|
+
export const dispatchScript = String.raw ` // ─── Autonomous dispatch: POST /tasks/dispatch and /tasks/abort ────────────
|
|
3
|
+
function runDispatch(taskId, customOptions) {
|
|
4
|
+
var key = 'task:' + taskId;
|
|
5
|
+
if (pipe.busy[key]) return;
|
|
6
|
+
pipe.busy[key] = 'dispatch';
|
|
7
|
+
refreshUi([]);
|
|
8
|
+
var payload = Object.assign({ taskId: taskId }, customOptions || {});
|
|
9
|
+
postJson('/api/pipeline/tasks/dispatch', payload).then(function (body) {
|
|
10
|
+
upsertRun(body && body.run);
|
|
11
|
+
var engine = body && body.run && body.run.engine === 'native' ? 'native engine' : 'CLI runner';
|
|
12
|
+
toast('Dispatched ' + taskId + ' to the ' + engine, 'ok');
|
|
13
|
+
openConsole(taskId, true);
|
|
14
|
+
}, function (e) {
|
|
15
|
+
toast('Could not dispatch ' + taskId + ': ' + e.message);
|
|
16
|
+
}).then(function () {
|
|
17
|
+
delete pipe.busy[key];
|
|
18
|
+
return fetchPipeline(['status', 'tasks', 'runs', 'worktrees']);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runAbort(taskId) {
|
|
23
|
+
var key = 'task:' + taskId;
|
|
24
|
+
if (pipe.busy[key]) return;
|
|
25
|
+
pipe.busy[key] = 'abort';
|
|
26
|
+
refreshUi([]);
|
|
27
|
+
postJson('/api/pipeline/tasks/abort', { taskId: taskId, reason: 'Aborted from Nativ Studio' }).then(function () {
|
|
28
|
+
toast('Abort signal sent to ' + taskId, 'ok');
|
|
29
|
+
}, function (e) {
|
|
30
|
+
toast('Could not abort ' + taskId + ': ' + e.message);
|
|
31
|
+
}).then(function () {
|
|
32
|
+
delete pipe.busy[key];
|
|
33
|
+
return fetchPipeline(['status', 'tasks', 'runs']);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var pendingConfirm = null;
|
|
38
|
+
function confirmDialog(opts, onConfirm) {
|
|
39
|
+
$('confirm-title').textContent = opts.title;
|
|
40
|
+
$('confirm-body').textContent = opts.body;
|
|
41
|
+
var ok = $('btn-confirm-ok');
|
|
42
|
+
ok.textContent = opts.confirmLabel;
|
|
43
|
+
ok.className = 'btn ' + (opts.danger ? 'danger-solid' : 'primary');
|
|
44
|
+
pendingConfirm = onConfirm;
|
|
45
|
+
openDialog($('confirm-dialog'));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function confirmAbort(taskId) {
|
|
49
|
+
confirmDialog({
|
|
50
|
+
title: 'Abort agent run',
|
|
51
|
+
body: 'Terminate the Claude process tree for ' + taskId + '? The isolated worktree and its branch are left in place so the partial work can be inspected.',
|
|
52
|
+
confirmLabel: 'Abort Run',
|
|
53
|
+
danger: true
|
|
54
|
+
}, function () { runAbort(taskId); });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Intelligent dispatch modal (#dispatch-dialog) ─────────────────────────
|
|
58
|
+
var pendingDispatch = null;
|
|
59
|
+
|
|
60
|
+
/** Mirrors buildDefaultClaudeCommand() in agent-supervisor.ts so the preview matches what the server runs. */
|
|
61
|
+
function defaultClaudeCommand(t) {
|
|
62
|
+
var prompt = [
|
|
63
|
+
'Execute task ' + t.id + ' (' + t.title + ').',
|
|
64
|
+
t.description ? 'Description: ' + t.description + '.' : '',
|
|
65
|
+
t.verificationCommand ? 'Verify your work using: ' + t.verificationCommand + '.' : '',
|
|
66
|
+
'Start by running: nativ task start ' + t.id + '. When finished and verified, run: nativ task complete ' + t.id + '.',
|
|
67
|
+
'If blocked, follow the Human-Centric Communication Protocol in CLAUDE.md: explain the user experience symptom, root cause in plain English, and clear options without technical jargon.'
|
|
68
|
+
].filter(Boolean).join(' ');
|
|
69
|
+
// No permission bypass: acceptEdits plus the per-task allowlist the server writes to .nativ/runs/permissions/<task>.json.
|
|
70
|
+
return 'claude -p "' + prompt.replace(/"/g, '\\"') + '" --output-format stream-json --verbose --permission-mode acceptEdits --settings ".nativ/runs/permissions/' + t.id + '.json"';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function syncDispatchSwitches() {
|
|
74
|
+
var isolated = $('dispatch-worktree').checked;
|
|
75
|
+
var merge = $('dispatch-merge');
|
|
76
|
+
merge.disabled = !isolated || !$('dispatch-verify-gate').checked;
|
|
77
|
+
if (merge.disabled) merge.checked = false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkedValue(name) {
|
|
81
|
+
var el = document.querySelector('input[name="' + name + '"]:checked');
|
|
82
|
+
return el ? el.value : '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Current Claude models reject a fixed thinking budget, so the native engine maps it onto an
|
|
87
|
+
* effort level (same buckets as thinkingBudgetToEffort in agent-supervisor.ts). Say so here
|
|
88
|
+
* rather than implying an exact token cap.
|
|
89
|
+
*/
|
|
90
|
+
function budgetHint(engine, budget) {
|
|
91
|
+
if (engine === 'cli') {
|
|
92
|
+
return budget ? 'Passed to Claude Code as MAX_THINKING_TOKENS=' + budget + '.' : 'Claude Code chooses its own thinking budget.';
|
|
93
|
+
}
|
|
94
|
+
if (!budget) return 'Runs at low effort, the fastest setting (Claude Opus 5.5 cannot switch thinking off entirely).';
|
|
95
|
+
var effort = budget <= 2048 ? 'medium' : budget <= 8192 ? 'high' : budget <= 32768 ? 'xhigh' : 'max';
|
|
96
|
+
return 'Runs at ' + effort + ' effort: the native engine maps token budgets onto effort levels.';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function syncDispatchEngine() {
|
|
100
|
+
var engine = checkedValue('dispatch-engine') || 'native';
|
|
101
|
+
$('dispatch-command-field').hidden = engine !== 'cli';
|
|
102
|
+
$('dispatch-budget-hint').textContent = budgetHint(engine, Number(checkedValue('dispatch-budget')) || 0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function openDispatchDialog(taskId) {
|
|
106
|
+
var t = taskById(taskId);
|
|
107
|
+
if (!t) { runDispatch(taskId); return; }
|
|
108
|
+
var files = t.targetFiles || [];
|
|
109
|
+
pendingDispatch = { taskId: t.id, preview: defaultClaudeCommand(t) };
|
|
110
|
+
$('dispatch-task-id').textContent = t.id;
|
|
111
|
+
$('dispatch-agent').textContent = t.assignedSubagent || 'unassigned';
|
|
112
|
+
$('dispatch-task-title').textContent = t.title || '';
|
|
113
|
+
$('dispatch-files').innerHTML = files.map(function (f) { return '<li class="file" title="' + esc(f) + '">' + esc(f) + '</li>'; }).join('');
|
|
114
|
+
$('dispatch-files').hidden = !files.length;
|
|
115
|
+
$('dispatch-verify').innerHTML = t.verificationCommand ? '<span class="cmd-label">Test command: </span>' + esc(t.verificationCommand) : 'no test command';
|
|
116
|
+
$('dispatch-command').value = pendingDispatch.preview;
|
|
117
|
+
$('dispatch-engine-native').checked = true;
|
|
118
|
+
$('dispatch-budget-none').checked = true;
|
|
119
|
+
syncDispatchEngine();
|
|
120
|
+
$('dispatch-worktree').checked = true;
|
|
121
|
+
$('dispatch-verify-gate').checked = !!t.verificationCommand;
|
|
122
|
+
$('dispatch-verify-gate').disabled = !t.verificationCommand;
|
|
123
|
+
$('dispatch-merge').checked = false;
|
|
124
|
+
$('dispatch-error').textContent = '';
|
|
125
|
+
syncDispatchSwitches();
|
|
126
|
+
openDialog($('dispatch-dialog'));
|
|
127
|
+
$('btn-launch-agent').focus();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function launchDispatch() {
|
|
131
|
+
if (!pendingDispatch) return;
|
|
132
|
+
var engine = checkedValue('dispatch-engine') || 'native';
|
|
133
|
+
var budget = Number(checkedValue('dispatch-budget')) || 0;
|
|
134
|
+
var command = $('dispatch-command').value.trim();
|
|
135
|
+
if (engine === 'cli' && !command) {
|
|
136
|
+
$('dispatch-error').textContent = 'Enter a runner command, or reopen the dialog to restore the generated one.';
|
|
137
|
+
$('dispatch-command').focus();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
var opts = {
|
|
141
|
+
runnerEngine: engine,
|
|
142
|
+
useWorktree: $('dispatch-worktree').checked,
|
|
143
|
+
verify: $('dispatch-verify-gate').checked,
|
|
144
|
+
autoMerge: $('dispatch-merge').checked
|
|
145
|
+
};
|
|
146
|
+
// Always sent: 0 ("None") asks for the least thinking, which differs from leaving the model default.
|
|
147
|
+
opts.thinkingBudget = budget;
|
|
148
|
+
// An untouched preview is the server's own default, which may be overridden by NATIV_RUNNER_COMMAND.
|
|
149
|
+
if (engine === 'cli' && command !== pendingDispatch.preview) opts.runnerCommand = command;
|
|
150
|
+
var id = pendingDispatch.taskId;
|
|
151
|
+
pendingDispatch = null;
|
|
152
|
+
$('dispatch-dialog').close();
|
|
153
|
+
runDispatch(id, opts);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const eventsToolsScript: string;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Console, strategist chip and dispatch modal event wiring.
|
|
2
|
+
export const eventsToolsScript = String.raw ` // ─── Events: live runner console drawer ────────────────────────────────────
|
|
3
|
+
$('rc-autoscroll').addEventListener('click', function () {
|
|
4
|
+
rc.autoscroll = !rc.autoscroll;
|
|
5
|
+
this.setAttribute('aria-pressed', String(rc.autoscroll));
|
|
6
|
+
if (rc.autoscroll) $('rc-body').scrollTop = $('rc-body').scrollHeight;
|
|
7
|
+
});
|
|
8
|
+
$('rc-clear').addEventListener('click', function () {
|
|
9
|
+
$('rc-body').innerHTML = '<span class="rc-empty">Console cleared. New output will stream in here.</span>';
|
|
10
|
+
});
|
|
11
|
+
$('rc-abort').addEventListener('click', function () { if (rc.taskId) confirmAbort(rc.taskId); });
|
|
12
|
+
document.querySelectorAll('.rc-tab').forEach(function (btn) {
|
|
13
|
+
btn.addEventListener('click', function () { setConsoleTab(btn.getAttribute('data-rc-tab')); });
|
|
14
|
+
btn.addEventListener('keydown', function (e) {
|
|
15
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
var tabs = visibleConsoleTabs();
|
|
18
|
+
var i = tabs.indexOf(rc.tab);
|
|
19
|
+
setConsoleTab(tabs[(i + (e.key === 'ArrowRight' ? 1 : tabs.length - 1)) % tabs.length], true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
$('rc-diff-refresh').addEventListener('click', function () { if (rc.taskId) loadConsoleDiff(rc.taskId); });
|
|
23
|
+
$('rc-heal').addEventListener('click', function (e) {
|
|
24
|
+
var opt = e.target.closest('[data-t1-opt]');
|
|
25
|
+
if (opt) {
|
|
26
|
+
t1Choice.index = Number(opt.getAttribute('data-t1-opt'));
|
|
27
|
+
$('rc-heal').querySelectorAll('[data-t1-opt]').forEach(function (b) {
|
|
28
|
+
b.setAttribute('aria-checked', String(b === opt));
|
|
29
|
+
b.tabIndex = b === opt ? 0 : -1;
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
var btn = e.target.closest('[data-heal]');
|
|
34
|
+
if (!btn || btn.disabled) return;
|
|
35
|
+
var what = btn.getAttribute('data-heal');
|
|
36
|
+
if (what === 'retry') { pipe.loaded.escalations = false; renderHeal(); fetchPipeline(['escalations']); }
|
|
37
|
+
else if (what === 't1-run') runTriage(rc.taskId ? escalationFor(rc.taskId) : null);
|
|
38
|
+
else if (what === 't1-decide') commitDecision('approve');
|
|
39
|
+
else if (what === 't1-dismiss') commitDecision('reject');
|
|
40
|
+
else resolveEscalation(what);
|
|
41
|
+
});
|
|
42
|
+
$('rc-heal').addEventListener('input', function (e) {
|
|
43
|
+
if (e.target.id === 't1-custom') t1Choice.custom = e.target.value;
|
|
44
|
+
});
|
|
45
|
+
// Decision options form a radio group: arrow keys move the selection.
|
|
46
|
+
$('rc-heal').addEventListener('keydown', function (e) {
|
|
47
|
+
var opt = e.target.closest && e.target.closest('[data-t1-opt]');
|
|
48
|
+
if (!opt || ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].indexOf(e.key) === -1) return;
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
var all = Array.prototype.slice.call($('rc-heal').querySelectorAll('[data-t1-opt]'));
|
|
51
|
+
var step = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1 : all.length - 1;
|
|
52
|
+
var next = all[(all.indexOf(opt) + step) % all.length];
|
|
53
|
+
next.click();
|
|
54
|
+
next.focus();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ─── Events: Tier 1 strategist chip ────────────────────────────────────────
|
|
58
|
+
$('t1-chip').addEventListener('click', function () { setTriageMenu($('t1-menu').hidden); });
|
|
59
|
+
$('t1-auto').addEventListener('click', function () { setAutoTriage(!(pipe.triage && pipe.triage.autoTriageEnabled)); });
|
|
60
|
+
document.addEventListener('click', function (e) {
|
|
61
|
+
if (!$('t1-menu').hidden && !e.target.closest('#t1')) setTriageMenu(false);
|
|
62
|
+
});
|
|
63
|
+
$('t1').addEventListener('keydown', function (e) {
|
|
64
|
+
if (e.key !== 'Escape' || $('t1-menu').hidden) return;
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
setTriageMenu(false);
|
|
67
|
+
$('t1-chip').focus();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ─── Events: dispatch modal ────────────────────────────────────────────────
|
|
71
|
+
document.querySelectorAll('input[name="dispatch-engine"], input[name="dispatch-budget"]').forEach(function (input) {
|
|
72
|
+
input.addEventListener('change', syncDispatchEngine);
|
|
73
|
+
});
|
|
74
|
+
$('dispatch-worktree').addEventListener('change', syncDispatchSwitches);
|
|
75
|
+
$('dispatch-verify-gate').addEventListener('change', syncDispatchSwitches);
|
|
76
|
+
$('btn-launch-agent').addEventListener('click', launchDispatch);
|
|
77
|
+
$('dispatch-dialog').addEventListener('keydown', function (e) {
|
|
78
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); launchDispatch(); }
|
|
79
|
+
});
|
|
80
|
+
$('dispatch-dialog').addEventListener('close', function () { pendingDispatch = null; });
|
|
81
|
+
$('rc-minimize').addEventListener('click', toggleConsoleMinimized);
|
|
82
|
+
$('rc-close').addEventListener('click', closeConsole);
|
|
83
|
+
$('runner-console-drawer').addEventListener('keydown', function (e) {
|
|
84
|
+
if (e.key === 'Escape') { e.stopPropagation(); closeConsole(); $('main').focus(); }
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
$('btn-confirm-ok').addEventListener('click', function () {
|
|
88
|
+
var fn = pendingConfirm;
|
|
89
|
+
pendingConfirm = null;
|
|
90
|
+
$('confirm-dialog').close();
|
|
91
|
+
if (fn) fn();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const formatScript: string;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ICON_PATHS, ST_SVG } from './icons.js';
|
|
2
|
+
// Pure client core: the Studio object, formatting and small html helpers. No DOM access.
|
|
3
|
+
export const formatScript = String.raw ` // ─── Studio client core: state, formatting and html helpers (pure) ─────────
|
|
4
|
+
var Studio = {
|
|
5
|
+
pages: {},
|
|
6
|
+
order: [],
|
|
7
|
+
model: null,
|
|
8
|
+
state: { view: 'home', milestoneId: null, focusTaskId: null, detailTaskId: null },
|
|
9
|
+
icons: ${JSON.stringify(ICON_PATHS)},
|
|
10
|
+
stSvg: ${JSON.stringify(ST_SVG)},
|
|
11
|
+
now: function () { return Date.now(); },
|
|
12
|
+
registerPage: function (id, def) { Studio.pages[id] = def || {}; },
|
|
13
|
+
go: function () {},
|
|
14
|
+
openTask: function () {},
|
|
15
|
+
closeDetail: function () {},
|
|
16
|
+
repaint: function () {},
|
|
17
|
+
toast: function () {},
|
|
18
|
+
taskAction: function () {}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function esc(value) {
|
|
22
|
+
return String(value == null ? '' : value).replace(/[&<>"']/g, function (ch) {
|
|
23
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var STATUS_LABEL = { completed: 'Done', in_progress: 'Running', pending: 'Up next', blocked: 'Stuck' };
|
|
28
|
+
var AGENTS = {
|
|
29
|
+
'backend': ['Backend', '--ag-backend'],
|
|
30
|
+
'frontend': ['Frontend', '--ag-frontend'],
|
|
31
|
+
'qa-tester': ['QA tester', '--ag-qa'],
|
|
32
|
+
'flutter-developer': ['Flutter', '--ag-flutter'],
|
|
33
|
+
'architect': ['Architect', '--ag-architect'],
|
|
34
|
+
'devops-agent': ['DevOps', '--ag-devops'],
|
|
35
|
+
'database': ['Database', '--ag-database'],
|
|
36
|
+
'security-auditor': ['Security', '--ag-security'],
|
|
37
|
+
'db-migration': ['DB migration', '--ag-database'],
|
|
38
|
+
'backend-agent': ['Backend', '--ag-backend'],
|
|
39
|
+
'frontend-agent': ['Frontend', '--ag-frontend'],
|
|
40
|
+
'qa-agent': ['QA tester', '--ag-qa'],
|
|
41
|
+
'database-agent': ['Database', '--ag-database']
|
|
42
|
+
};
|
|
43
|
+
/** Label and colour token for an agent id; unknown ids keep their raw id and the neutral colour. */
|
|
44
|
+
function agentInfo(id) {
|
|
45
|
+
var known = AGENTS[id];
|
|
46
|
+
return known ? { label: known[0], token: known[1] } : { label: String(id || 'unassigned'), token: '--fg-3' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function pad2(n) { return (n < 10 ? '0' : '') + n; }
|
|
50
|
+
|
|
51
|
+
var fmt = {
|
|
52
|
+
dur: function (ms) {
|
|
53
|
+
if (ms == null || isNaN(ms)) return '–';
|
|
54
|
+
var s = Math.round(ms / 1000);
|
|
55
|
+
if (s < 60) return s + 's';
|
|
56
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ' + pad2(s % 60) + 's';
|
|
57
|
+
return Math.floor(s / 3600) + 'h ' + pad2(Math.floor((s % 3600) / 60)) + 'm';
|
|
58
|
+
},
|
|
59
|
+
ago: function (t) {
|
|
60
|
+
if (!t) return 'time not recorded';
|
|
61
|
+
var s = Math.round((Studio.now() - t) / 1000);
|
|
62
|
+
if (s < 45) return 'just now';
|
|
63
|
+
if (s < 3600) return Math.round(s / 60) + 'm ago';
|
|
64
|
+
if (s < 86400) return Math.round(s / 3600) + 'h ago';
|
|
65
|
+
return Math.round(s / 86400) + 'd ago';
|
|
66
|
+
},
|
|
67
|
+
clock: function (t) { return new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); },
|
|
68
|
+
day: function (t) { return new Date(t).toLocaleDateString([], { month: 'short', day: 'numeric' }); },
|
|
69
|
+
full: function (t) {
|
|
70
|
+
if (!t) return 'not recorded';
|
|
71
|
+
return new Date(t).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
72
|
+
},
|
|
73
|
+
money: function (v) { return v == null || isNaN(v) ? '–' : '$' + Number(v).toFixed(2); }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Grounded spend is small per task: four decimals below a dollar. */
|
|
77
|
+
function usdSpend(n) {
|
|
78
|
+
if (n == null || isNaN(n)) return '–';
|
|
79
|
+
n = Number(n);
|
|
80
|
+
return '$' + (n >= 1 ? n.toFixed(2) : n.toFixed(4));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Wraps inner SVG paths (24-unit box, stroke icon) in an svg element. */
|
|
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>';
|
|
87
|
+
}
|
|
88
|
+
function svgIcon(name, width) { return svgWrap(Studio.icons[name], width); }
|
|
89
|
+
|
|
90
|
+
var ui = {
|
|
91
|
+
esc: esc,
|
|
92
|
+
icon: svgIcon,
|
|
93
|
+
svg: svgWrap,
|
|
94
|
+
statusIcon: function (status) {
|
|
95
|
+
var label = STATUS_LABEL[status] || 'Unknown';
|
|
96
|
+
var mark = status === 'in_progress' ? '<span class="pulse"></span>' : (Studio.stSvg[status] || Studio.stSvg.pending);
|
|
97
|
+
return '<span class="st ' + esc(status) + '" title="' + esc(label) + '">' + mark + '</span>';
|
|
98
|
+
},
|
|
99
|
+
agentChip: function (agent) {
|
|
100
|
+
var info = agentInfo(agent);
|
|
101
|
+
return '<span class="agent" style="--c:var(' + info.token + ')"><i></i>' + esc(info.label) + '</span>';
|
|
102
|
+
},
|
|
103
|
+
pill: function (status, text) {
|
|
104
|
+
return '<span class="status-pill ' + esc(status) + '">' + esc(text || STATUS_LABEL[status] || status) + '</span>';
|
|
105
|
+
},
|
|
106
|
+
when: function (t) {
|
|
107
|
+
if (!t) return '<time title="not recorded">time not recorded</time>';
|
|
108
|
+
return '<time datetime="' + new Date(t).toISOString() + '" title="' + esc(fmt.full(t)) + '">' + esc(fmt.ago(t)) + '</time>';
|
|
109
|
+
},
|
|
110
|
+
taskRow: function (task, rightText) {
|
|
111
|
+
var right = rightText != null ? rightText : (task.endT ? fmt.ago(task.endT) : '');
|
|
112
|
+
return '<button type="button" class="row-btn" data-task="' + esc(task.id) + '">' + ui.statusIcon(task.status) +
|
|
113
|
+
'<span class="mono faint hide-sm">' + esc(task.id) + '</span><span class="t">' + esc(task.title) + '</span>' +
|
|
114
|
+
'<span class="hide-sm">' + ui.agentChip(task.agent) + '</span>' +
|
|
115
|
+
'<span class="faint num row-right">' + esc(right) + '</span></button>';
|
|
116
|
+
},
|
|
117
|
+
milestoneSelect: function () {
|
|
118
|
+
var model = Studio.model;
|
|
119
|
+
if (!model) return '';
|
|
120
|
+
var current = Studio.state.milestoneId;
|
|
121
|
+
var opts = model.milestones.slice().reverse().map(function (m) {
|
|
122
|
+
var text = m.id + ' · ' + m.short + ' (' + m.done + '/' + m.total + ')' + (m.id === model.activeMilestoneId ? ' · active' : '');
|
|
123
|
+
return '<option value="' + esc(m.id) + '"' + (m.id === current ? ' selected' : '') + '>' + esc(text) + '</option>';
|
|
124
|
+
}).join('');
|
|
125
|
+
return '<select class="sel" data-ms-select aria-label="Milestone">' + opts + '</select>';
|
|
126
|
+
},
|
|
127
|
+
empty: function (title, text) {
|
|
128
|
+
return '<div class="ui-empty"><b>' + esc(title) + '</b>' + esc(text || '') + '</div>';
|
|
129
|
+
},
|
|
130
|
+
skeleton: function () {
|
|
131
|
+
var bar = function (w) { return '<div class="skel" style="width:' + w + '"></div>'; };
|
|
132
|
+
return '<div class="skel-head">' + bar('180px') + bar('320px') + '</div>' +
|
|
133
|
+
'<div class="skel-block">' + bar('100%') + bar('92%') + bar('74%') + '</div>' +
|
|
134
|
+
'<div class="skel-block">' + bar('100%') + bar('88%') + bar('60%') + '</div>';
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
Studio.fmt = fmt;
|
|
139
|
+
Studio.ui = ui;
|
|
140
|
+
Studio.agentInfo = agentInfo;
|
|
141
|
+
Studio.statusLabel = function (status) { return STATUS_LABEL[status] || 'Unknown'; };
|
|
142
|
+
|
|
143
|
+
var OLD_HASHES = { overview: 'home', canvas: 'flow', pods: 'team' };
|
|
144
|
+
/** Turns a location hash into a page id; old hashes redirect, unknown ones fall back to Home. */
|
|
145
|
+
Studio.resolveHash = function (hash, ids) {
|
|
146
|
+
var h = String(hash || '').replace(/^#/, '');
|
|
147
|
+
if (OLD_HASHES[h]) h = OLD_HASHES[h];
|
|
148
|
+
return ids.indexOf(h) !== -1 ? h : 'home';
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Inline SVG icon paths and status marks shared by the shell and every page.
|
|
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"/>',
|
|
14
|
+
};
|
|
15
|
+
// Done check, up-next dashed ring and stuck alert; running is a pulsing dot.
|
|
16
|
+
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>',
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function markupHead(version: string, projectName: string, projectHidden: string): string;
|