@njeybe/nativ 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +71 -1
- package/dist/commands/design-check.d.ts +4 -0
- package/dist/commands/design-check.js +76 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +53 -1
- package/dist/commands/update.js +46 -0
- package/dist/core/design-rules.d.ts +23 -0
- package/dist/core/design-rules.js +115 -0
- package/dist/core/harness-integrity.js +26 -7
- package/dist/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.js +8 -5
- package/dist/server/studio-ui/pages/database/browser.js +1 -1
- package/dist/server/studio-ui/pages/database/connection.js +1 -1
- package/dist/server/studio-ui/pages/database/data-shell.js +5 -5
- package/dist/server/studio-ui/pages/database/drift.js +1 -1
- package/dist/server/studio-ui/pages/database/explorer.js +2 -2
- package/dist/server/studio-ui/pages/database/styles.js +5 -5
- package/dist/server/studio-ui/pages/flow/styles.js +3 -3
- package/dist/server/studio-ui/pages/home/script.js +110 -91
- package/dist/server/studio-ui/pages/home/styles.js +33 -45
- package/dist/server/studio-ui/pages/tasks/script.js +150 -3
- package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
- package/dist/server/studio-ui/pages/team/script.js +49 -52
- package/dist/server/studio-ui/pages/team/styles.js +21 -20
- package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
- package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
- package/dist/server/studio-ui/shell/brand.d.ts +4 -0
- package/dist/server/studio-ui/shell/brand.js +16 -0
- package/dist/server/studio-ui/shell/console.js +61 -2
- package/dist/server/studio-ui/shell/core.js +8 -8
- package/dist/server/studio-ui/shell/detail-view.js +1 -1
- package/dist/server/studio-ui/shell/dispatch.js +18 -0
- package/dist/server/studio-ui/shell/events-tools.js +5 -1
- package/dist/server/studio-ui/shell/format.js +5 -6
- package/dist/server/studio-ui/shell/icons.js +23 -15
- package/dist/server/studio-ui/shell/markup-head.js +11 -6
- package/dist/server/studio-ui/shell/markup-tools.js +19 -10
- package/dist/server/studio-ui/shell/paint.js +3 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +1 -1
- package/dist/server/studio-ui/shell/proposals.js +6 -2
- package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
- package/dist/server/studio-ui/shell/pulse.js +67 -0
- package/dist/server/studio-ui/shell/router.js +25 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +6 -3
- package/dist/server/studio-ui/shell/toasts-format.js +4 -4
- package/dist/server/studio-ui/styles/console.js +33 -13
- package/dist/server/studio-ui/styles/dialogs.js +4 -4
- package/dist/server/studio-ui/styles/responsive.js +8 -0
- package/dist/server/studio-ui/styles/shell.js +24 -5
- package/dist/server/studio-ui/styles/strategist.js +10 -10
- package/dist/server/studio-ui/styles/tokens-base.js +23 -23
- package/dist/server/studio-ui.js +2 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/architect.md +10 -11
- package/templates/GEMINI.md +3 -2
- package/templates/claude-agents/architect.md +10 -11
- package/templates/design/taste/LICENSE +21 -0
- package/templates/design/taste/SOURCE.md +28 -0
- package/templates/design/taste/v1/DIGEST.md +57 -0
- package/templates/design/taste/v1/SKILL.md +226 -0
- package/templates/design/taste/v2/DIGEST.md +62 -0
- package/templates/design/taste/v2/SKILL.md +1206 -0
- package/templates/dot-ai/subagents/architect.md +6 -0
- package/templates/dot-ai/subagents/flutter-developer.md +2 -11
- package/templates/dot-ai/subagents/frontend.md +47 -44
- package/templates/dot-ai/ui_specs.md +15 -36
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Database Studio data loading and shell.
|
|
2
|
-
export const databaseDataScript = String.raw ` // ─── View 5: Database Studio
|
|
2
|
+
export const databaseDataScript = String.raw ` // ─── View 5: Database Studio - data loading ────────────────────────────────
|
|
3
3
|
function load(fresh) {
|
|
4
4
|
state.loading = true;
|
|
5
5
|
renderDb();
|
|
@@ -63,7 +63,7 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
63
63
|
? ' <span class="badge badge-source" title="Detected in ' + esc(s.exampleFile || '.env.example') + '">via ' + esc(s.sourceKey) + '</span>'
|
|
64
64
|
: (s.sourceKey ? ' <span class="badge badge-source">via ' + esc(s.sourceKey) + '</span>' : '');
|
|
65
65
|
return '<span class="pill" title="' + esc(s.maskedUrl) + '"><span class="dot on"></span><b>' + label + ':</b> ' +
|
|
66
|
-
esc(ENGINE_LABEL[s.engine] || s.engine) + (s.database ? ' · ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms)
|
|
66
|
+
esc(ENGINE_LABEL[s.engine] || s.engine) + (s.database ? ' · ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms): ' +
|
|
67
67
|
entityCount(s) + ' ' + entityNoun(s, true) + sourceBadge + ' <span class="badge badge-online">ONLINE</span></span>';
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -119,7 +119,7 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
119
119
|
: ((state.schema[activeEnv + 'Tables'] || []).length);
|
|
120
120
|
$('count-data').textContent = String(dataCount);
|
|
121
121
|
var d = state.diff && state.diff.summary;
|
|
122
|
-
$('count-drift').textContent = d ? String(d.addedTablesCount + d.alteredTablesCount + d.droppedTablesCount) : '
|
|
122
|
+
$('count-drift').textContent = d ? String(d.addedTablesCount + d.alteredTablesCount + d.droppedTablesCount) : '-';
|
|
123
123
|
var script = state.diff ? generateScript(state.diff) : { text: '', lang: 'sql', count: 0 };
|
|
124
124
|
state.sql = script.text;
|
|
125
125
|
state.scriptLang = script.lang;
|
|
@@ -154,8 +154,8 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
154
154
|
return '<div class="sr-only">Loading database schema…</div><div class="panes" aria-busy="true">' + col + col + '</div>';
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
var DB_ICON = '<svg class="icon empty-icon" width="56" height="56" viewBox="0 0
|
|
158
|
-
'<
|
|
157
|
+
var DB_ICON = '<svg class="icon empty-icon" width="56" height="56" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">' +
|
|
158
|
+
'<path d="M128,24C74.17,24,32,48.6,32,80v96c0,31.4,42.17,56,96,56s96-24.6,96-56V80C224,48.6,181.83,24,128,24Zm80,104c0,9.62-7.88,19.43-21.61,26.92C170.93,163.35,150.19,168,128,168s-42.93-4.65-58.39-13.08C55.88,147.43,48,137.62,48,128V111.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64ZM69.61,53.08C85.07,44.65,105.81,40,128,40s42.93,4.65,58.39,13.08C200.12,60.57,208,70.38,208,80s-7.88,19.43-21.61,26.92C170.93,115.35,150.19,120,128,120s-42.93-4.65-58.39-13.08C55.88,99.43,48,89.62,48,80S55.88,60.57,69.61,53.08ZM186.39,202.92C170.93,211.35,150.19,216,128,216s-42.93-4.65-58.39-13.08C55.88,195.43,48,185.62,48,176V159.36c17.06,15,46.23,24.64,80,24.64s62.94-9.68,80-24.64V176C208,185.62,200.12,195.43,186.39,202.92Z"/></svg>';
|
|
159
159
|
|
|
160
160
|
function emptyState(title, text) {
|
|
161
161
|
return '<div class="empty card">' + DB_ICON + '<h3>' + esc(title) + '</h3><p>' + esc(text) + '</p>' +
|
|
@@ -15,7 +15,7 @@ export const databaseDriftScript = String.raw ` // ─── Database Studio: d
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function colCell(c) {
|
|
18
|
-
if (!c) return '<span class="muted"
|
|
18
|
+
if (!c) return '<span class="muted">-</span>';
|
|
19
19
|
return esc(c.type) + (c.nullable ? '' : ' NOT NULL') + (c.default != null ? ' <span class="muted">DEFAULT</span> ' + esc(c.default) : '') +
|
|
20
20
|
(c.primaryKey ? ' <span class="badge badge-pk">PK</span>' : '') + (c.isMedia ? ' <span class="badge badge-media">IMAGE</span>' : '') +
|
|
21
21
|
(c.isSubcollection ? ' <span class="badge badge-subcol">SUBCOLLECTION</span>' : '');
|
|
@@ -32,9 +32,9 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
|
|
|
32
32
|
mediaBadge(t, c);
|
|
33
33
|
var typeCell = esc(c.type) + (c.observedTypes ? ' <span class="muted">(' + esc(c.observedTypes.join(' | ')) + ')</span>' : '');
|
|
34
34
|
var lastCell = coll
|
|
35
|
-
? (c.presence != null ? Math.round(c.presence * 100) + '%' : '<span class="muted"
|
|
35
|
+
? (c.presence != null ? Math.round(c.presence * 100) + '%' : '<span class="muted">-</span>')
|
|
36
36
|
: (c.default == null ? '<span class="muted">NULL</span>' : esc(c.default));
|
|
37
|
-
return '<tr><td>' + (tags || '<span class="muted"
|
|
37
|
+
return '<tr><td>' + (tags || '<span class="muted">-</span>') + '</td><td class="mono">' + esc(c.name) +
|
|
38
38
|
(fk ? ' <span class="muted">→ ' + esc(fk.referencedTable + '.' + fk.referencedColumn) + '</span>' : '') + '</td><td class="mono">' + typeCell +
|
|
39
39
|
'</td><td>' + (c.nullable ? 'YES' : '<b>NO</b>') + '</td><td class="mono">' + lastCell + '</td></tr>';
|
|
40
40
|
}).join('');
|
|
@@ -6,7 +6,7 @@ export const databaseCss = String.raw `/* Database Studio */
|
|
|
6
6
|
.diagnostic-banner { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 16px; margin: 12px 0 16px; background: var(--danger-bg); border: 1px solid var(--danger-line); border-radius: var(--r-control); }
|
|
7
7
|
.diagnostic-content { display: flex; align-items: center; gap: 10px; font-size: 13.5px; }
|
|
8
8
|
.diagnostic-tag { font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--danger); background: var(--surface); border: 1px solid var(--danger-line); padding: 0 7px; border-radius: var(--r-control); }
|
|
9
|
-
.diagnostic-msg code { background: var(--surface); padding: 1px 6px; border-radius:
|
|
9
|
+
.diagnostic-msg code { background: var(--surface); padding: 1px 6px; border-radius: var(--r-control); font-family: var(--mono); }
|
|
10
10
|
.diagnostic-actions { display: flex; gap: 8px; }
|
|
11
11
|
.template-helper-box { background: var(--active-bg); border: 1px solid var(--active-line); border-radius: var(--r-control); padding: 12px 14px; margin-bottom: 16px; }
|
|
12
12
|
.template-helper-label { font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
|
|
@@ -18,7 +18,7 @@ export const databaseCss = String.raw `/* Database Studio */
|
|
|
18
18
|
.chip-dot.on { background: var(--success); }
|
|
19
19
|
.chip-dot.off { background: var(--danger); }
|
|
20
20
|
|
|
21
|
-
.thumb-pop { position: fixed; z-index: 60; width: 220px; padding: 8px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow
|
|
21
|
+
.thumb-pop { position: fixed; z-index: 60; width: 220px; padding: 8px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow); pointer-events: none; }
|
|
22
22
|
.thumb-box { display: grid; place-items: center; min-height: 120px; max-height: 200px; overflow: hidden; border-radius: var(--r-control); background: repeating-conic-gradient(var(--chip) 0% 25%, var(--surface) 0% 50%) 50% / 16px 16px; }
|
|
23
23
|
.thumb-box img { max-width: 100%; max-height: 200px; display: block; }
|
|
24
24
|
.thumb-box .ph { color: var(--muted); font-size: 12px; text-align: center; padding: 12px; }
|
|
@@ -77,7 +77,7 @@ pre.sql { margin: 0; padding: 16px; overflow: auto; max-height: 70vh; background
|
|
|
77
77
|
.sql .p { color: var(--active); } .sql .n { color: var(--active); font-weight: 600; } .sql .o { color: var(--primary); }
|
|
78
78
|
.lang { font-family: var(--mono); font-size: 11px; padding: 0 7px; border-radius: var(--r-control); border: 1px solid var(--primary-line); background: var(--primary-tint); color: var(--primary); margin-right: 6px; }
|
|
79
79
|
.copy-wrap { position: relative; display: inline-flex; }
|
|
80
|
-
.tooltip { position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%); background: var(--success); color:
|
|
80
|
+
.tooltip { position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%); background: var(--success); color: var(--accent-fg); font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: var(--r-control); pointer-events: none; opacity: 0; transition: opacity .15s; white-space: nowrap; }
|
|
81
81
|
.tooltip.show { opacity: 1; }
|
|
82
82
|
|
|
83
83
|
.empty { text-align: center; padding: 48px 24px; }
|
|
@@ -87,7 +87,7 @@ pre.sql { margin: 0; padding: 16px; overflow: auto; max-height: 70vh; background
|
|
|
87
87
|
|
|
88
88
|
.env-toggle-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin: 14px 0 6px; }
|
|
89
89
|
.env-segmented { display: inline-flex; gap: 2px; padding: 3px; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r-control); }
|
|
90
|
-
.env-seg-btn { border: 1px solid transparent; background: transparent; padding: 4px 14px; border-radius:
|
|
90
|
+
.env-seg-btn { border: 1px solid transparent; background: transparent; padding: 4px 14px; border-radius: var(--r-control); cursor: pointer; color: var(--muted); font-size: 13px; font-weight: 600; transition: all .15s ease; }
|
|
91
91
|
.env-seg-btn:hover { color: var(--text); }
|
|
92
92
|
.env-seg-btn[aria-pressed="true"] { background: var(--surface); color: var(--primary); border-color: var(--border); box-shadow: var(--shadow); }
|
|
93
93
|
.env-seg-btn[data-env-mode="prod"][aria-pressed="true"] { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-line); }
|
|
@@ -112,7 +112,7 @@ table.data-table tr:hover td { background: var(--bg); }
|
|
|
112
112
|
.td-actions { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
|
|
113
113
|
.pagination-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 4px; font-size: 13px; color: var(--muted); }
|
|
114
114
|
.pagination-ctrls { display: flex; align-items: center; gap: 8px; }
|
|
115
|
-
.cell-preview-btn { padding: 1px 6px; font-size: 11px; border-radius:
|
|
115
|
+
.cell-preview-btn { padding: 1px 6px; font-size: 11px; border-radius: var(--r-control); border: 1px solid var(--primary-line); color: var(--primary); background: var(--primary-tint); cursor: pointer; }
|
|
116
116
|
.cell-preview-btn:hover { background: var(--surface); }
|
|
117
117
|
|
|
118
118
|
`;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Flow page styles, all scoped under #panel-flow.
|
|
2
|
-
export const flowCss = String.raw `#panel-flow .seg { display: inline-flex; border: 1px solid var(--line); border-radius:
|
|
2
|
+
export const flowCss = String.raw `#panel-flow .seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-control); overflow: hidden; background: var(--surface); }
|
|
3
3
|
#panel-flow .seg button { border: 0; background: none; padding: 0 12px; height: 30px; font-size: 13px; color: var(--fg-2); cursor: pointer; }
|
|
4
4
|
#panel-flow .seg button + button { border-left: 1px solid var(--line); }
|
|
5
5
|
#panel-flow .seg button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
|
|
6
|
-
#panel-flow .fl-panel { background: var(--surface); border: 1px solid var(--line); border-radius:
|
|
6
|
+
#panel-flow .fl-panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card); min-width: 0; overflow: hidden; }
|
|
7
7
|
#panel-flow .fl-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; padding: 12px 16px; }
|
|
8
8
|
#panel-flow .fl-bar .right { margin-left: auto; display: flex; gap: 14px; align-items: center; flex-wrap: wrap; font-size: 12.5px; color: var(--fg-3); }
|
|
9
9
|
#panel-flow .flow-wrap { overflow: auto; max-height: 72vh; position: relative; border-top: 1px solid var(--line); }
|
|
@@ -18,7 +18,7 @@ export const flowCss = String.raw `#panel-flow .seg { display: inline-flex; bord
|
|
|
18
18
|
#panel-flow .lane-label { position: sticky; left: 0; width: 128px; padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; background: var(--surface); border-right: 1px solid var(--line); height: 100%; z-index: 2; }
|
|
19
19
|
#panel-flow .lane-label .faint { font-size: 11.5px; }
|
|
20
20
|
#panel-flow .round-head { position: absolute; top: 0; height: 30px; display: flex; align-items: center; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-3); }
|
|
21
|
-
#panel-flow .node { position: absolute; display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 7px 10px; background: var(--surface); border: 1px solid var(--line); border-radius:
|
|
21
|
+
#panel-flow .node { position: absolute; display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 7px 10px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-control); text-align: left; cursor: pointer; transition: opacity .15s, border-color .15s, box-shadow .15s; z-index: 1; }
|
|
22
22
|
#panel-flow .node:hover { border-color: var(--line-2); box-shadow: var(--shadow); }
|
|
23
23
|
#panel-flow .node.in_progress { border-color: color-mix(in srgb, var(--run) 50%, var(--line)); background: color-mix(in srgb, var(--run-soft) 55%, var(--surface)); }
|
|
24
24
|
#panel-flow .node.blocked { border-color: color-mix(in srgb, var(--bad) 50%, var(--line)); background: color-mix(in srgb, var(--bad-soft) 55%, var(--surface)); }
|
|
@@ -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
|
|
3
|
+
var UP_NEXT_CAP = 6;
|
|
4
|
+
var ACTIVITY_CAP = 8;
|
|
4
5
|
|
|
5
6
|
function head() {
|
|
6
|
-
return '<div class="page-head"><
|
|
7
|
+
return '<div class="page-head"><h1>Home</h1></div>';
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
function
|
|
10
|
-
return '<section class="
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
42
|
-
var
|
|
43
|
-
|
|
44
|
-
'<div class="
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
50
|
-
var
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
63
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
76
|
-
return '<
|
|
77
|
-
|
|
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
|
|
81
|
-
var
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
88
|
-
var
|
|
89
|
-
|
|
90
|
-
var
|
|
91
|
-
var
|
|
92
|
-
return '<
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
'<span class="
|
|
107
|
-
|
|
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
|
|
112
|
-
var list =
|
|
113
|
-
var
|
|
114
|
-
|
|
115
|
-
var
|
|
116
|
-
|
|
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
|
|
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
|
-
|
|
122
|
-
'<span class="t"
|
|
123
|
-
'<
|
|
124
|
-
|
|
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
|
|
128
|
-
var list = model
|
|
129
|
-
var body = list.length ?
|
|
130
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
145
|
-
'<div class="home
|
|
146
|
-
|
|
147
|
-
|
|
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: {
|
|
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 .
|
|
3
|
-
#panel-home .page-head h1 { font-size: 22px; font-weight: 600; margin: 0; letter-spacing: -.01em;
|
|
4
|
-
#panel-home .
|
|
5
|
-
#panel-home .
|
|
6
|
-
#panel-home .
|
|
7
|
-
#panel-home .
|
|
8
|
-
#panel-home .
|
|
9
|
-
#panel-home .
|
|
10
|
-
#panel-home .
|
|
11
|
-
#panel-home .
|
|
12
|
-
#panel-home .
|
|
13
|
-
#panel-home .
|
|
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 .
|
|
16
|
-
#panel-home .
|
|
17
|
-
#panel-home .
|
|
18
|
-
#panel-home .
|
|
19
|
-
#panel-home .
|
|
20
|
-
|
|
21
|
-
#panel-home .
|
|
22
|
-
#panel-home .
|
|
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
|
`;
|