@njeybe/nativ 2.2.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +15 -0
  3. package/dist/server/studio-ui/document.d.ts +3 -0
  4. package/dist/server/studio-ui/document.js +22 -0
  5. package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
  6. package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
  7. package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
  8. package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
  9. package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
  10. package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
  11. package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
  12. package/dist/server/studio-ui/pages/database/browser.js +326 -0
  13. package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
  14. package/dist/server/studio-ui/pages/database/connection.js +158 -0
  15. package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
  16. package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
  17. package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
  18. package/dist/server/studio-ui/pages/database/drift.js +301 -0
  19. package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
  20. package/dist/server/studio-ui/pages/database/events.js +231 -0
  21. package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
  22. package/dist/server/studio-ui/pages/database/explorer.js +183 -0
  23. package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
  24. package/dist/server/studio-ui/pages/database/index.js +28 -0
  25. package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
  26. package/dist/server/studio-ui/pages/database/markup.js +32 -0
  27. package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
  28. package/dist/server/studio-ui/pages/database/state.js +57 -0
  29. package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
  30. package/dist/server/studio-ui/pages/database/styles.js +118 -0
  31. package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
  32. package/dist/server/studio-ui/pages/flow/index.js +11 -0
  33. package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
  34. package/dist/server/studio-ui/pages/flow/layout.js +105 -0
  35. package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
  36. package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
  37. package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
  38. package/dist/server/studio-ui/pages/flow/script.js +61 -0
  39. package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
  40. package/dist/server/studio-ui/pages/flow/styles.js +59 -0
  41. package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
  42. package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
  43. package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
  44. package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
  45. package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
  46. package/dist/server/studio-ui/pages/home/index.js +11 -0
  47. package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
  48. package/dist/server/studio-ui/pages/home/script.js +160 -0
  49. package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
  50. package/dist/server/studio-ui/pages/home/styles.js +48 -0
  51. package/dist/server/studio-ui/pages/index.d.ts +2 -0
  52. package/dist/server/studio-ui/pages/index.js +17 -0
  53. package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
  54. package/dist/server/studio-ui/pages/tasks/index.js +11 -0
  55. package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
  56. package/dist/server/studio-ui/pages/tasks/script.js +96 -0
  57. package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
  58. package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
  59. package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
  60. package/dist/server/studio-ui/pages/team/index.js +11 -0
  61. package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
  62. package/dist/server/studio-ui/pages/team/script.js +112 -0
  63. package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
  64. package/dist/server/studio-ui/pages/team/styles.js +22 -0
  65. package/dist/server/studio-ui/pages/types.d.ts +13 -0
  66. package/dist/server/studio-ui/pages/types.js +1 -0
  67. package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
  68. package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
  69. package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
  70. package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
  71. package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
  72. package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
  73. package/dist/server/studio-ui/shell/actions.d.ts +1 -0
  74. package/dist/server/studio-ui/shell/actions.js +49 -0
  75. package/dist/server/studio-ui/shell/boot.d.ts +2 -0
  76. package/dist/server/studio-ui/shell/boot.js +13 -0
  77. package/dist/server/studio-ui/shell/console.d.ts +1 -0
  78. package/dist/server/studio-ui/shell/console.js +194 -0
  79. package/dist/server/studio-ui/shell/core.d.ts +1 -0
  80. package/dist/server/studio-ui/shell/core.js +50 -0
  81. package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
  82. package/dist/server/studio-ui/shell/detail-view.js +80 -0
  83. package/dist/server/studio-ui/shell/detail.d.ts +1 -0
  84. package/dist/server/studio-ui/shell/detail.js +83 -0
  85. package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
  86. package/dist/server/studio-ui/shell/dispatch.js +156 -0
  87. package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
  88. package/dist/server/studio-ui/shell/events-tools.js +94 -0
  89. package/dist/server/studio-ui/shell/format.d.ts +1 -0
  90. package/dist/server/studio-ui/shell/format.js +151 -0
  91. package/dist/server/studio-ui/shell/icons.d.ts +2 -0
  92. package/dist/server/studio-ui/shell/icons.js +20 -0
  93. package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
  94. package/dist/server/studio-ui/shell/markup-head.js +43 -0
  95. package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
  96. package/dist/server/studio-ui/shell/markup-panels.js +7 -0
  97. package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
  98. package/dist/server/studio-ui/shell/markup-tools.js +222 -0
  99. package/dist/server/studio-ui/shell/model.d.ts +1 -0
  100. package/dist/server/studio-ui/shell/model.js +192 -0
  101. package/dist/server/studio-ui/shell/paint.d.ts +1 -0
  102. package/dist/server/studio-ui/shell/paint.js +154 -0
  103. package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
  104. package/dist/server/studio-ui/shell/palette-view.js +60 -0
  105. package/dist/server/studio-ui/shell/palette.d.ts +1 -0
  106. package/dist/server/studio-ui/shell/palette.js +80 -0
  107. package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
  108. package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
  109. package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
  110. package/dist/server/studio-ui/shell/proposals.js +197 -0
  111. package/dist/server/studio-ui/shell/pure.d.ts +3 -0
  112. package/dist/server/studio-ui/shell/pure.js +9 -0
  113. package/dist/server/studio-ui/shell/router.d.ts +1 -0
  114. package/dist/server/studio-ui/shell/router.js +95 -0
  115. package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
  116. package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
  117. package/dist/server/studio-ui/shell/sync.d.ts +1 -0
  118. package/dist/server/studio-ui/shell/sync.js +120 -0
  119. package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
  120. package/dist/server/studio-ui/shell/toasts-format.js +89 -0
  121. package/dist/server/studio-ui/shell/triage.d.ts +1 -0
  122. package/dist/server/studio-ui/shell/triage.js +210 -0
  123. package/dist/server/studio-ui/styles/console.d.ts +1 -0
  124. package/dist/server/studio-ui/styles/console.js +83 -0
  125. package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
  126. package/dist/server/studio-ui/styles/dialogs.js +118 -0
  127. package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
  128. package/dist/server/studio-ui/styles/responsive.js +28 -0
  129. package/dist/server/studio-ui/styles/shell.d.ts +1 -0
  130. package/dist/server/studio-ui/styles/shell.js +117 -0
  131. package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
  132. package/dist/server/studio-ui/styles/strategist.js +77 -0
  133. package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
  134. package/dist/server/studio-ui/styles/tokens-base.js +131 -0
  135. package/dist/server/studio-ui.d.ts +1 -10
  136. package/dist/server/studio-ui.js +63 -5314
  137. package/package.json +2 -2
  138. package/plugin/.claude-plugin/plugin.json +1 -1
@@ -0,0 +1,112 @@
1
+ // Team page client script: one pure render, one table row per agent.
2
+ export const teamScript = String.raw ` var esc = Studio.ui.esc;
3
+ var DASH = '–';
4
+
5
+ function head() {
6
+ return '<div class="page-head"><div><h1>Team</h1><p>Each agent in plain terms: what it did, how long it took, what it cost.</p></div></div>';
7
+ }
8
+
9
+ function sum(list) {
10
+ return list.reduce(function (total, n) { return total + n; }, 0);
11
+ }
12
+
13
+ function statsFor(key, tasks) {
14
+ var done = tasks.filter(function (t) { return t.status === 'completed'; });
15
+ var timed = done.filter(function (t) { return t.durationMs; });
16
+ var work = sum(timed.map(function (t) { return t.durationMs; }));
17
+ var costs = tasks.filter(function (t) { return t.costUsd != null; });
18
+ var stamps = tasks.map(function (t) { return t.status === 'in_progress' ? (t.startT || 0) : (t.endT || 0); });
19
+ return {
20
+ agent: key || null,
21
+ total: tasks.length,
22
+ done: done.length,
23
+ work: timed.length ? work : null,
24
+ avg: timed.length ? work / timed.length : null,
25
+ cost: costs.length ? sum(costs.map(function (t) { return t.costUsd; })) : null,
26
+ last: Math.max.apply(null, [0].concat(stamps)) || null,
27
+ running: tasks.filter(function (t) { return t.status === 'in_progress'; })[0] || null,
28
+ stuck: tasks.filter(function (t) { return t.status === 'blocked'; })[0] || null
29
+ };
30
+ }
31
+
32
+ function buildRows(model) {
33
+ var groups = {};
34
+ var order = [];
35
+ model.tasks.forEach(function (t) {
36
+ var key = t.agent || '';
37
+ if (!groups[key]) { groups[key] = []; order.push(key); }
38
+ groups[key].push(t);
39
+ });
40
+ return order.map(function (k) { return statsFor(k, groups[k]); })
41
+ .sort(function (a, b) { return b.total - a.total; });
42
+ }
43
+
44
+ function chip(task, label) {
45
+ return '<button type="button" class="chip" data-task="' + esc(task.id) + '">' +
46
+ Studio.ui.statusIcon(task.status) + esc(label) + '</button>';
47
+ }
48
+
49
+ function rightNow(r) {
50
+ if (r.running) return chip(r.running, r.running.id);
51
+ if (r.stuck) return chip(r.stuck, 'Stuck on ' + r.stuck.id);
52
+ return '<span class="faint">Idle</span>';
53
+ }
54
+
55
+ function shareBar(r, max) {
56
+ var info = Studio.agentInfo(r.agent);
57
+ var pct = max ? Math.round((r.total / max) * 100) : 0;
58
+ return '<div class="bar"><i style="width:' + pct + '%;background:var(' + info.token + ')"></i></div>';
59
+ }
60
+
61
+ function lastActive(r) {
62
+ return r.last ? Studio.ui.when(r.last) : DASH;
63
+ }
64
+
65
+ function row(r, max) {
66
+ return '<tr data-agent="' + esc(r.agent || 'unassigned') + '"><td>' + Studio.ui.agentChip(r.agent) + '</td>' +
67
+ '<td>' + rightNow(r) + '</td>' +
68
+ '<td class="r">' + r.done + '<span class="faint"> / ' + r.total + '</span></td>' +
69
+ '<td class="share">' + shareBar(r, max) + '</td>' +
70
+ '<td class="r">' + (r.avg == null ? DASH : esc(Studio.fmt.dur(r.avg))) + '</td>' +
71
+ '<td class="r">' + (r.work == null ? DASH : esc(Studio.fmt.dur(r.work))) + '</td>' +
72
+ '<td class="r">' + (r.cost == null ? DASH : esc(Studio.fmt.money(r.cost))) + '</td>' +
73
+ '<td class="r faint">' + lastActive(r) + '</td></tr>';
74
+ }
75
+
76
+ function table(rows) {
77
+ var max = Math.max.apply(null, rows.map(function (r) { return r.total; }));
78
+ var cols = ['Agent', 'Right now', 'Tasks done', 'Share of work', 'Avg. time', 'Total time', 'Est. cost', 'Last active'];
79
+ var th = cols.map(function (c, i) {
80
+ var cls = i === 3 ? ' class="share"' : (i > 1 ? ' class="r"' : '');
81
+ return '<th' + cls + '>' + c + '</th>';
82
+ }).join('');
83
+ return '<section class="panel table-wrap"><table><thead><tr>' + th + '</tr></thead><tbody>' +
84
+ rows.map(function (r) { return row(r, max); }).join('') + '</tbody></table></section>';
85
+ }
86
+
87
+ function defs() {
88
+ return '<section class="panel"><div class="panel-head"><h2>What these columns mean</h2></div><div class="panel-body defs">' +
89
+ '<div><b>Tasks done</b>: finished and checked, out of all tasks given to this agent.</div>' +
90
+ '<div><b>Avg. time</b>: from start to a passing check, for tasks that recorded timing.</div>' +
91
+ '<div><b>Est. cost</b>: estimated from token counts. The real bill is on Home under Spend.</div>' +
92
+ '<div><b>Right now</b>: the task this agent is working on, or the one it is stuck on.</div></div></section>';
93
+ }
94
+
95
+ function skeleton() {
96
+ var bar = function (w) { return '<div class="skel" style="width:' + w + '"></div>'; };
97
+ var block = '<div class="skel-block">' + bar('100%') + bar('92%') + bar('74%') + '</div>';
98
+ return head() + block + block;
99
+ }
100
+
101
+ function render(ctx) {
102
+ var rows = buildRows(ctx.model);
103
+ if (!rows.length) return head() + '<section class="panel">' + ctx.ui.empty('No tasks yet', 'Agents show up here once the plan has tasks.') + '</section>';
104
+ return head() + table(rows) + defs();
105
+ }
106
+
107
+ Studio.registerPage('team', {
108
+ deps: ['tasks', 'telemetry', 'runs'],
109
+ render: render,
110
+ skeleton: skeleton
111
+ });
112
+ `;
@@ -0,0 +1 @@
1
+ export declare const teamCss: string;
@@ -0,0 +1,22 @@
1
+ // Team page styles, all scoped under #panel-team.
2
+ export const teamCss = String.raw `#panel-team .page-head { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
3
+ #panel-team .page-head h1 { font-size: 22px; font-weight: 600; margin: 0; letter-spacing: -.01em; text-wrap: balance; }
4
+ #panel-team .page-head p { margin: 4px 0 0; color: var(--fg-2); }
5
+ #panel-team .panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; min-width: 0; margin-top: 16px; }
6
+ #panel-team .panel-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
7
+ #panel-team .panel-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
8
+ #panel-team .panel-body { padding: 14px 16px; }
9
+ #panel-team .table-wrap { overflow-x: auto; }
10
+ #panel-team table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
11
+ #panel-team th { text-align: left; font-weight: 500; font-size: 12px; color: var(--fg-3); padding: 10px 16px; border-bottom: 1px solid var(--line); white-space: nowrap; }
12
+ #panel-team td { padding: 11px 16px; border-bottom: 1px solid var(--line); white-space: nowrap; }
13
+ #panel-team tr:last-child td { border-bottom: 0; }
14
+ #panel-team td.r, #panel-team th.r { text-align: right; }
15
+ #panel-team .share { width: 110px; }
16
+ #panel-team .bar { height: 6px; border-radius: 3px; background: var(--surface-2); overflow: hidden; }
17
+ #panel-team .bar i { display: block; height: 100%; border-radius: 3px; }
18
+ #panel-team .chip { display: inline-flex; align-items: center; gap: 6px; padding: 2px 10px 2px 6px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); font: 12px var(--mono); cursor: pointer; }
19
+ #panel-team .chip:hover { background: var(--surface-2); }
20
+ #panel-team .defs { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px 24px; font-size: 13px; color: var(--fg-2); }
21
+ #panel-team .defs b { color: var(--fg); font-weight: 500; }
22
+ `;
@@ -0,0 +1,13 @@
1
+ export interface PageModule {
2
+ id: string;
3
+ group: 'now' | 'project' | 'system';
4
+ label: string;
5
+ /** Inner SVG markup for a 24-unit viewBox (stroke icon). */
6
+ icon: string;
7
+ /** Styles scoped under #panel-<id>. */
8
+ css: string;
9
+ /** Client script; the shell wraps it in its own function scope and passes Studio. */
10
+ script: string;
11
+ /** Static markup placed inside the page panel before the first paint. */
12
+ markup?: string;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { PageModule } from '../types.js';
2
+ export declare const worktreesPage: PageModule;
@@ -0,0 +1,11 @@
1
+ import { ICON_PATHS } from '../../shell/icons.js';
2
+ import { worktreesCss } from './styles.js';
3
+ import { worktreesScript } from './script.js';
4
+ export const worktreesPage = {
5
+ id: 'worktrees',
6
+ group: 'project',
7
+ label: 'Worktrees',
8
+ icon: ICON_PATHS.worktrees,
9
+ css: worktreesCss,
10
+ script: worktreesScript,
11
+ };
@@ -0,0 +1 @@
1
+ export declare const worktreesScript: string;
@@ -0,0 +1,104 @@
1
+ // Worktrees view script.
2
+ export const worktreesScript = String.raw ` // ─── View 3: Agent worktrees ───────────────────────────────────────────────
3
+ function shortPath(p) {
4
+ var parts = String(p || '').split(/[\\/]+/).filter(Boolean);
5
+ return parts.length > 2 ? '…/' + parts.slice(-2).join('/') : String(p || '');
6
+ }
7
+
8
+ function worktreeTaskStatus(w) {
9
+ if (w.taskStatus) return w.taskStatus;
10
+ var task = w.taskId ? taskById(w.taskId) : null;
11
+ return task ? task.status : null;
12
+ }
13
+
14
+ function worktreeBadge(w, status) {
15
+ if (!w.isAgentWorktree) return '<span class="badge badge-neutral">Main workspace</span>';
16
+ if (status === 'completed') return '<span class="badge badge-success">Tests Passed (Ready to merge)</span>';
17
+ if (status === 'in_progress') return '<span class="badge badge-active">Agent working</span>';
18
+ if (status === 'blocked') return '<span class="badge badge-danger">Blocked</span>';
19
+ if (status === 'pending') return '<span class="badge badge-neutral">Pending</span>';
20
+ return '<span class="badge badge-neutral" title="No matching task in the master plan">Untracked</span>';
21
+ }
22
+
23
+ function worktreeRow(w) {
24
+ var status = worktreeTaskStatus(w);
25
+ var task = w.taskId ? taskById(w.taskId) : null;
26
+ // The server decides merge eligibility (and why not); fall back to the task status from the plan.
27
+ var canMerge = w.mergeEligible != null ? !!w.mergeEligible : status === 'completed';
28
+ var blockedWhy = w.mergeBlockedReason || 'Available once the task is completed';
29
+ var busy = w.taskId && pipe.busy['wt:' + w.taskId];
30
+ var actions = '<span class="muted">–</span>';
31
+ if (w.isAgentWorktree && w.taskId) {
32
+ actions = busy
33
+ ? '<button class="btn small" type="button" disabled><span class="spinner" aria-hidden="true"></span>' + (busy === 'merge' ? 'Merging…' : 'Deleting…') + '</button>'
34
+ : '<button class="btn small" type="button" data-act="wt-diff" data-key="' + esc(w.taskId) + '" aria-label="' + esc('Inspect diff for ' + w.taskId) + '">Inspect Diff</button>' +
35
+ '<button class="btn small primary" type="button" data-act="wt-merge" data-key="' + esc(w.taskId) + '"' + (canMerge ? '' : ' disabled title="' + esc(blockedWhy) + '"') + '>Merge to Main</button>' +
36
+ '<button class="btn small danger" type="button" data-act="wt-remove" data-key="' + esc(w.taskId) + '">Delete Workspace</button>';
37
+ }
38
+ return '<tr><td data-label="Branch"><code>' + esc(w.branch) + '</code></td>' +
39
+ '<td data-label="Task ID">' + (w.taskId ? '<div><code>' + esc(w.taskId) + '</code>' + (task ? '<div class="cell-sub" title="' + esc(task.title) + '">' + esc(task.title) + '</div>' : '') + '</div>' : '<span class="muted">–</span>') + '</td>' +
40
+ '<td data-label="Workspace Folder"><code class="path" title="' + esc(w.path) + '">' + esc(shortPath(w.path)) + '</code></td>' +
41
+ '<td data-label="Latest Commit"><code>' + esc(String(w.head || '').slice(0, 7) || '–') + '</code></td>' +
42
+ '<td data-label="Status">' + worktreeBadge(w, status) + '</td>' +
43
+ '<td data-label="Actions"><div class="row-actions">' + actions + '</div></td></tr>';
44
+ }
45
+
46
+ function renderWorktrees() {
47
+ var head = panelHead('Agent Worktrees', 'Isolated git workspaces for tasks. Changes are tested automatically before merging into your main branch.');
48
+ if (!pipe.loaded.worktrees) return head + '<div class="card section" aria-hidden="true"><div class="skeleton"></div><div class="skeleton"></div><div class="skeleton short"></div></div>';
49
+ if (pipe.errors.worktrees && !pipe.worktrees.length) return head + errorCard('worktrees', pipe.errors.worktrees, 'worktrees');
50
+ var rows = pipe.worktrees.slice().sort(function (a, b) { return (a.isAgentWorktree ? 1 : 0) - (b.isAgentWorktree ? 1 : 0); });
51
+ if (!rows.some(function (w) { return w.isAgentWorktree; })) {
52
+ return head + staleNote('worktrees') + '<div class="card state"><div class="state-icon">' + ICON.branch + '</div><h2>No agent worktrees</h2>' +
53
+ '<p>Run <code>nativ worktree create &lt;taskId&gt;</code> to give an agent its own isolated workspace folder for an independent task.</p></div>';
54
+ }
55
+ return head + staleNote('worktrees') + '<div class="card wt-card"><table class="grid"><thead><tr>' +
56
+ '<th scope="col">Branch Name</th><th scope="col">Task ID</th><th scope="col">Workspace Folder</th><th scope="col">Latest Commit</th><th scope="col">Status</th><th scope="col">Actions</th>' +
57
+ '</tr></thead><tbody>' + rows.map(worktreeRow).join('') + '</tbody></table></div>';
58
+ }
59
+
60
+ function confirmWorktree(action, taskId) {
61
+ var w = null;
62
+ pipe.worktrees.some(function (x) { if (x.taskId === taskId) { w = x; return true; } return false; });
63
+ var branch = w ? w.branch : 'agent/task-' + taskId;
64
+ if (action === 'merge') {
65
+ confirmDialog({
66
+ title: 'Merge to Main',
67
+ body: 'Merge ' + branch + ' into the main workspace? Automated tests will run first and reject the merge if tests fail.',
68
+ confirmLabel: 'Merge to Main'
69
+ }, function () { runWorktreeAction('merge', taskId); });
70
+ } else {
71
+ confirmDialog({
72
+ title: 'Delete workspace',
73
+ body: 'Remove the workspace folder for ' + taskId + ' and delete branch ' + branch + '? Uncommitted work will be removed.',
74
+ confirmLabel: 'Delete Workspace',
75
+ danger: true
76
+ }, function () { runWorktreeAction('remove', taskId); });
77
+ }
78
+ }
79
+
80
+ function runWorktreeAction(action, taskId) {
81
+ var key = 'wt:' + taskId;
82
+ if (pipe.busy[key]) return;
83
+ pipe.busy[key] = action;
84
+ repaint('worktrees');
85
+ postJson('/api/pipeline/worktrees/action', { action: action, taskId: taskId }).then(function (r) {
86
+ toast(r.message || (action === 'merge' ? 'Merged workspace for ' : 'Deleted workspace for ') + taskId, 'ok');
87
+ }, function (e) {
88
+ toast((action === 'merge' ? 'Merge rejected for ' : 'Could not delete workspace for ') + taskId + ': ' + e.message);
89
+ }).then(function () {
90
+ delete pipe.busy[key];
91
+ return fetchPipeline(['worktrees', 'tasks', 'status']);
92
+ });
93
+ }
94
+
95
+ Studio.registerPage('worktrees', {
96
+ deps: ['worktrees', 'tasks'],
97
+ render: function () { return renderWorktrees(); },
98
+ actions: {
99
+ 'wt-diff': function (el) { openConsole(el.getAttribute('data-key'), false, 'diff'); },
100
+ 'wt-merge': function (el) { confirmWorktree('merge', el.getAttribute('data-key')); },
101
+ 'wt-remove': function (el) { confirmWorktree('remove', el.getAttribute('data-key')); }
102
+ }
103
+ });
104
+ `;
@@ -0,0 +1 @@
1
+ export declare const worktreesCss: string;
@@ -0,0 +1,15 @@
1
+ // Worktrees view styles.
2
+ export const worktreesCss = String.raw `/* Agent worktrees */
3
+ .wt-card { overflow: hidden; }
4
+ table.grid { width: 100%; border-collapse: collapse; font-size: 13.5px; }
5
+ table.grid th { padding: 10px 16px; text-align: left; font-size: 12.5px; font-weight: 600; color: var(--muted); background: var(--bg); border-bottom: 1px solid var(--border); white-space: nowrap; }
6
+ table.grid th:last-child { text-align: right; }
7
+ table.grid td { padding: 12px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
8
+ table.grid tbody tr:last-child td { border-bottom: none; }
9
+ table.grid tbody tr:hover td { background: var(--bg); }
10
+ table.grid code { font-size: 12.5px; }
11
+ .cell-sub { max-width: 280px; margin-top: 2px; font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
12
+ .path { display: inline-block; max-width: 260px; vertical-align: bottom; font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
13
+ .row-actions { display: flex; justify-content: flex-end; gap: 6px; }
14
+
15
+ `;
@@ -0,0 +1 @@
1
+ export declare const actionsScript: string;
@@ -0,0 +1,49 @@
1
+ // Task actions behind the detail panel and the pages: start, dispatch, stop, logs, proposals.
2
+ export const actionsScript = String.raw ` // ─── Task actions ──────────────────────────────────────────────────────────
3
+ /** Plain-language failure text: no stack, one closing period. */
4
+ function plainFail(what, e) {
5
+ return what + ': ' + String((e && e.message) || 'unknown error').replace(/[.\s]+$/, '') + '.';
6
+ }
7
+
8
+ function runTaskAction(action, taskId) {
9
+ var key = 'task:' + taskId;
10
+ if (pipe.busy[key]) return;
11
+ pipe.busy[key] = action;
12
+ refreshUi([]);
13
+ postJson('/api/pipeline/tasks/action', { action: action, taskId: taskId }).then(function () {
14
+ toast('Started ' + taskId, 'ok');
15
+ }, function (e) {
16
+ toast(plainFail('Could not start ' + taskId, e));
17
+ }).then(function () {
18
+ delete pipe.busy[key];
19
+ return fetchPipeline(['status', 'tasks', 'worktrees']);
20
+ });
21
+ }
22
+
23
+ function askArchitect(taskId) {
24
+ var escalation = escalationFor(taskId);
25
+ if (!escalation) { toast('There is nothing waiting for the architect on ' + taskId + '.'); return; }
26
+ openConsole(taskId, true, 'heal');
27
+ runTriage(escalation);
28
+ }
29
+
30
+ var TASK_ACTIONS = {
31
+ start: function (id) { runTaskAction('start', id); },
32
+ dispatch: function (id) { openDispatchDialog(id); },
33
+ abort: function (id) { confirmAbort(id); },
34
+ logs: function (id) { openConsole(id, false, 'logs'); },
35
+ output: function (id) { openConsole(id, false, 'logs'); },
36
+ proposal: function (id) { openConsole(id, false, 'heal'); },
37
+ ask: askArchitect,
38
+ flow: function (id) {
39
+ var t = Studio.model && Studio.model.byId[id];
40
+ if (t) Studio.go('flow', { milestoneId: t.milestoneId, focusTaskId: id, keepDetail: true });
41
+ }
42
+ };
43
+ Studio.taskAction = function (name, taskId) {
44
+ if (TASK_ACTIONS[name]) TASK_ACTIONS[name](taskId);
45
+ };
46
+ /** Name of the action running for a task, or null. */
47
+ Studio.busyAction = function (taskId) { return pipe.busy['task:' + taskId] || null; };
48
+
49
+ `;
@@ -0,0 +1,2 @@
1
+ import type { PageModule } from '../pages/types.js';
2
+ export declare function bootScript(pages: PageModule[]): string;
@@ -0,0 +1,13 @@
1
+ // Startup: pages register, the first view opens, data loads and the live stream connects.
2
+ export function bootScript(pages) {
3
+ const order = JSON.stringify(pages.map((p) => ({ id: p.id, group: p.group, label: p.label, icon: p.icon })));
4
+ return String.raw ` // ─── Boot ──────────────────────────────────────────────────────────────────
5
+ Studio.order = ${order};
6
+ setView(Studio.resolveHash(location.hash, pageIds()));
7
+ fetchPipeline(ALL_PARTS);
8
+ connectEvents();
9
+ })();
10
+ </script>
11
+ </body>
12
+ </html>`;
13
+ }
@@ -0,0 +1 @@
1
+ export declare const consoleScript: string;
@@ -0,0 +1,194 @@
1
+ // Runner console drawer and worktree diff tab.
2
+ export const consoleScript = String.raw ` // ─── Live runner console drawer ────────────────────────────────────────────
3
+ var RC_STEPS = ['Worktree', 'Claude', 'Verify', 'Merge'];
4
+ var RC_STAGE = { spawning_worktree: 0, running: 1, verifying: 2, merging: 3 };
5
+ var RC_MAX_NODES = 3000;
6
+ var rc = { taskId: null, autoscroll: true, minimized: false, stage: 0, ticker: null, backfilled: false, tab: 'logs', diffSeq: 0, diffTimer: null, heal: null };
7
+
8
+ /** Minimal SGR parser: escapes the chunk, then maps the colors agents actually emit. */
9
+ var ANSI_CLASS = {
10
+ '0': null, '39': null, '1': 'rc-bold', '2': 'rc-fg-muted', '90': 'rc-fg-muted',
11
+ '31': 'rc-fg-danger', '91': 'rc-fg-danger', '32': 'rc-fg-ok', '92': 'rc-fg-ok',
12
+ '33': 'rc-fg-warn', '93': 'rc-fg-warn', '34': 'rc-fg-info', '94': 'rc-fg-info',
13
+ '36': 'rc-fg-info', '96': 'rc-fg-info'
14
+ };
15
+ function ansiToHtml(text, fallbackClass) {
16
+ var out = '', open = 0, cls = fallbackClass || '';
17
+ var parts = String(text == null ? '' : text).split(/\x1b\[([0-9;]*)m/);
18
+ for (var i = 0; i < parts.length; i++) {
19
+ if (i % 2 === 1) {
20
+ var codes = parts[i].split(';');
21
+ for (var c = 0; c < codes.length; c++) {
22
+ var code = codes[c] || '0';
23
+ if (!Object.prototype.hasOwnProperty.call(ANSI_CLASS, code)) continue;
24
+ while (open > 0) { out += '</span>'; open--; }
25
+ cls = ANSI_CLASS[code] ? ANSI_CLASS[code] : (fallbackClass || '');
26
+ }
27
+ continue;
28
+ }
29
+ // Other CSI sequences (cursor moves, clears) carry no meaning in a log pane.
30
+ var chunk = parts[i].replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '').replace(/\x1b\][^\x07\x1b]*(\x07|\x1b\\)/g, '');
31
+ if (!chunk) continue;
32
+ if (cls) { out += '<span class="' + cls + '">' + esc(chunk) + '</span>'; }
33
+ else out += esc(chunk);
34
+ }
35
+ while (open > 0) { out += '</span>'; open--; }
36
+ return out;
37
+ }
38
+
39
+ function rcStepper(run) {
40
+ var status = run ? run.status : null;
41
+ var stage = RC_STAGE[status] != null ? RC_STAGE[status] : rc.stage;
42
+ var settled = !!status && !RUN_ACTIVE[status];
43
+ var failed = status === 'failed' || status === 'aborted';
44
+ if (RC_STAGE[status] != null) rc.stage = stage;
45
+ return RC_STEPS.map(function (label, i) {
46
+ var cls = 'rc-step';
47
+ // Never dispatched (e.g. opened from a proposal banner): no phase is active.
48
+ if (!status) return '<li class="' + cls + '">' + esc(label) + '</li>';
49
+ if (status === 'completed') cls += ' is-done';
50
+ else if (settled && failed) cls += i < stage ? ' is-done' : (i === stage ? ' is-failed' : '');
51
+ else if (i < stage) cls += ' is-done';
52
+ else if (i === stage) cls += ' is-active';
53
+ return '<li class="' + cls + '">' + esc(label) + '</li>';
54
+ }).join('<li class="rc-sep" aria-hidden="true">&#8250;</li>');
55
+ }
56
+
57
+ function syncConsole() {
58
+ var run = rc.taskId ? runFor(rc.taskId) : null;
59
+ var task = rc.taskId ? taskById(rc.taskId) : null;
60
+ $('rc-task').textContent = rc.taskId || '–';
61
+ $('rc-title').textContent = task ? task.title : '';
62
+ $('rc-steps').innerHTML = rcStepper(run);
63
+ $('rc-abort').disabled = !isRunActive(run);
64
+ $('rc-elapsed').textContent = mmss(elapsedOf(run) || 0);
65
+ $('rc-bytes').textContent = formatBytes((run && run.logBytes) || 0);
66
+ var gate = 'Idle';
67
+ if (run && run.verification) gate = run.verification.success ? 'Passed' : 'Failed';
68
+ else if (run && run.status === 'verifying') gate = 'Running';
69
+ $('rc-gate').textContent = gate;
70
+ // Native runs report grounded usage; cli runs have none to show.
71
+ var usage = run && run.usage;
72
+ $('rc-spend-wrap').hidden = !usage;
73
+ $('rc-cache-wrap').hidden = !usage;
74
+ if (usage) {
75
+ $('rc-spend').textContent = usdSpend(usage.costUsd);
76
+ $('rc-cache').textContent = Math.round((Number(usage.cacheHitRate) || 0) * 100) + '%';
77
+ }
78
+ var exit = '';
79
+ if (run && !isRunActive(run)) {
80
+ exit = RUN_LABEL[run.status] || run.status;
81
+ if (typeof run.exitCode === 'number') exit += ' (exit ' + run.exitCode + ')';
82
+ if (run.error) exit += ' · ' + run.error;
83
+ }
84
+ $('rc-exit').textContent = exit;
85
+ }
86
+
87
+ function rcTick() {
88
+ clearInterval(rc.ticker);
89
+ rc.ticker = setInterval(function () {
90
+ var run = rc.taskId ? runFor(rc.taskId) : null;
91
+ if (!rc.taskId || !isRunActive(run)) { clearInterval(rc.ticker); rc.ticker = null; return; }
92
+ $('rc-elapsed').textContent = mmss(elapsedOf(run) || 0);
93
+ }, 1000);
94
+ }
95
+
96
+ function appendConsole(html) {
97
+ var body = $('rc-body');
98
+ var stick = rc.autoscroll || body.scrollTop + body.clientHeight >= body.scrollHeight - 4;
99
+ var frag = document.createElement('span');
100
+ frag.innerHTML = html;
101
+ body.appendChild(frag);
102
+ while (body.childElementCount > RC_MAX_NODES) body.removeChild(body.firstChild);
103
+ if (stick) body.scrollTop = body.scrollHeight;
104
+ }
105
+
106
+ function loadConsoleLogs(taskId) {
107
+ return api('/api/pipeline/tasks/logs?taskId=' + encodeURIComponent(taskId) + '&tailLines=500').then(function (body) {
108
+ if (rc.taskId !== taskId) return;
109
+ $('rc-body').innerHTML = body.log ? ansiToHtml(body.log) : '<span class="rc-empty">No output captured yet.</span>';
110
+ $('rc-body').scrollTop = $('rc-body').scrollHeight;
111
+ rc.backfilled = true;
112
+ syncConsole();
113
+ }, function () {
114
+ if (rc.taskId !== taskId) return;
115
+ $('rc-body').innerHTML = '<span class="rc-empty">Waiting for the runner to emit output…</span>';
116
+ rc.backfilled = true;
117
+ });
118
+ }
119
+
120
+ // ─── Console tab 2: worktree git diff (GET /api/pipeline/worktrees/diff) ───
121
+ function diffToHtml(diff) {
122
+ return String(diff).split('\n').map(function (line) {
123
+ var cls = '';
124
+ if (/^(diff --git|\+\+\+ |--- )/.test(line)) cls = /^diff --git/.test(line) ? 'df-file' : 'df-meta';
125
+ else if (/^@@/.test(line)) cls = 'df-hunk';
126
+ else if (/^\+/.test(line)) cls = 'df-add';
127
+ else if (/^-/.test(line)) cls = 'df-del';
128
+ else if (/^(index |new file mode|deleted file mode|similarity |rename |Binary files)/.test(line)) cls = 'df-meta';
129
+ return cls ? '<span class="' + cls + '">' + esc(line) + '</span>' : esc(line) + '\n';
130
+ }).join('');
131
+ }
132
+
133
+ function setDiffState(summary, files, bodyHtml, count) {
134
+ $('rc-diff-summary').innerHTML = summary;
135
+ $('rc-diff-files').innerHTML = (files || []).map(function (f) { return '<li title="' + esc(f) + '">' + esc(f) + '</li>'; }).join('');
136
+ $('rc-diff-files').hidden = !(files && files.length);
137
+ $('rc-diff-body').innerHTML = bodyHtml;
138
+ $('rc-diff-count').textContent = count == null ? '–' : String(count);
139
+ }
140
+
141
+ function loadConsoleDiff(taskId) {
142
+ var seq = ++rc.diffSeq;
143
+ $('rc-diff-refresh').disabled = true;
144
+ if (!$('rc-diff-body').textContent) setDiffState('Loading worktree changes…', [], '<span class="rc-empty">Reading git status…</span>', null);
145
+ return api('/api/pipeline/worktrees/diff?taskId=' + encodeURIComponent(taskId)).then(function (body) {
146
+ if (seq !== rc.diffSeq || rc.taskId !== taskId) return;
147
+ var files = Array.isArray(body.filesChanged) ? body.filesChanged : [];
148
+ var summary = '<code>' + esc(body.branch || '') + '</code> · ' + (body.hasChanges ? files.length + ' uncommitted file' + (files.length === 1 ? '' : 's') : 'working tree clean');
149
+ setDiffState(summary, files, body.diff ? diffToHtml(body.diff) : '<span class="rc-empty">No uncommitted changes in this worktree yet.</span>', files.length);
150
+ }, function (e) {
151
+ if (seq !== rc.diffSeq || rc.taskId !== taskId) return;
152
+ var missing = e.code === 'WORKTREE_NOT_FOUND';
153
+ setDiffState(missing ? 'No isolated worktree' : 'Could not load the diff',
154
+ [], '<span class="rc-empty">' + esc(missing ? 'This task has no agent worktree. Dispatch it with Isolated Worktree on, or run nativ worktree create ' + taskId + '.' : e.message) + '</span>', null);
155
+ }).then(function () {
156
+ if (seq === rc.diffSeq) $('rc-diff-refresh').disabled = false;
157
+ });
158
+ }
159
+
160
+ /** Throttled refresh (at most every 2s) while an agent streams output or git state changes. */
161
+ function queueConsoleDiff() {
162
+ if (!rc.taskId || rc.tab !== 'diff' || rc.minimized || rc.diffTimer) return;
163
+ var id = rc.taskId;
164
+ rc.diffTimer = setTimeout(function () {
165
+ rc.diffTimer = null;
166
+ if (rc.taskId === id && rc.tab === 'diff') loadConsoleDiff(id);
167
+ }, 2000);
168
+ }
169
+
170
+ function setConsoleTab(tab, focus) {
171
+ if (tab === 'heal' && $('rc-tab-heal').hidden) tab = 'logs';
172
+ rc.tab = tab;
173
+ ['logs', 'diff', 'heal'].forEach(function (name) {
174
+ var btn = $('rc-tab-' + name);
175
+ var on = name === tab;
176
+ btn.setAttribute('aria-selected', String(on));
177
+ btn.setAttribute('tabindex', on ? '0' : '-1');
178
+ });
179
+ $('rc-body').hidden = tab !== 'logs';
180
+ $('rc-diff').hidden = tab !== 'diff';
181
+ $('rc-heal').hidden = tab !== 'heal';
182
+ $('rc-autoscroll').hidden = tab !== 'logs';
183
+ $('rc-clear').hidden = tab !== 'logs';
184
+ if (tab === 'diff' && rc.taskId) loadConsoleDiff(rc.taskId);
185
+ if (tab === 'heal') renderHeal();
186
+ if (focus) $('rc-tab-' + tab).focus();
187
+ }
188
+
189
+ /** Console tabs in DOM order, skipping the proposal tab while it is hidden. */
190
+ function visibleConsoleTabs() {
191
+ return ['logs', 'diff', 'heal'].filter(function (name) { return !$('rc-tab-' + name).hidden; });
192
+ }
193
+
194
+ `;
@@ -0,0 +1 @@
1
+ export declare const coreScript: string;
@@ -0,0 +1,50 @@
1
+ // Script opening, DOM lookup, fetch helpers and the icons used by the kept tools.
2
+ export const coreScript = String.raw `<script>
3
+ (function () {
4
+ 'use strict';
5
+
6
+ var $ = function (id) { return document.getElementById(id); };
7
+
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>'
17
+ };
18
+
19
+ function formatBytes(n) {
20
+ if (n == null) return 'unknown';
21
+ if (n < 1024) return n + ' B';
22
+ if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
23
+ return (n / 1048576).toFixed(2) + ' MB';
24
+ }
25
+
26
+ function api(path, options) {
27
+ return fetch(path, options).then(function (res) {
28
+ return res.json().catch(function () { return {}; }).then(function (body) {
29
+ // DB routes answer { error: { code, message } }; pipeline routes answer { ok: false, error: "..." }.
30
+ if (!res.ok || body.ok === false) {
31
+ var message = typeof body.error === 'string' ? body.error : (body.error && body.error.message) || body.message;
32
+ var err = new Error(message || ('Request failed (' + res.status + ')'));
33
+ err.code = (body.error && body.error.code) || body.code;
34
+ err.status = res.status;
35
+ throw err;
36
+ }
37
+ return body;
38
+ });
39
+ });
40
+ }
41
+
42
+ function postJson(path, body) {
43
+ return api(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
44
+ }
45
+
46
+ function openDialog(dlg) {
47
+ if (typeof dlg.showModal === 'function') dlg.showModal(); else dlg.setAttribute('open', '');
48
+ }
49
+
50
+ `;
@@ -0,0 +1 @@
1
+ export declare const detailViewScript: string;