@njeybe/nativ 2.6.0 → 2.8.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 +50 -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/core/setup/pm-playbook.d.ts +1 -1
- package/dist/core/setup/pm-playbook.js +1 -1
- package/dist/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +9 -8
- package/dist/server/studio-ui/pages/benchmarks/styles.js +11 -6
- package/dist/server/studio-ui/pages/database/browser.js +2 -2
- package/dist/server/studio-ui/pages/database/connection.js +6 -5
- package/dist/server/studio-ui/pages/database/data-shell.js +12 -17
- package/dist/server/studio-ui/pages/database/drift.js +2 -2
- package/dist/server/studio-ui/pages/database/events.js +1 -2
- package/dist/server/studio-ui/pages/database/explorer.js +6 -6
- package/dist/server/studio-ui/pages/database/styles.js +5 -5
- package/dist/server/studio-ui/pages/flow/layout.js +1 -1
- package/dist/server/studio-ui/pages/flow/styles.js +4 -4
- package/dist/server/studio-ui/pages/flow/timeline-view.js +1 -1
- package/dist/server/studio-ui/pages/home/script.js +108 -92
- package/dist/server/studio-ui/pages/home/styles.js +29 -45
- package/dist/server/studio-ui/pages/tasks/script.js +146 -14
- package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
- package/dist/server/studio-ui/pages/team/script.js +69 -53
- package/dist/server/studio-ui/pages/team/styles.js +20 -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 +71 -6
- package/dist/server/studio-ui/shell/core.js +8 -8
- package/dist/server/studio-ui/shell/detail-view.js +4 -4
- package/dist/server/studio-ui/shell/dispatch.js +23 -4
- package/dist/server/studio-ui/shell/events-tools.js +16 -1
- package/dist/server/studio-ui/shell/format.js +36 -8
- 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 +29 -15
- package/dist/server/studio-ui/shell/model.js +8 -2
- package/dist/server/studio-ui/shell/paint.js +4 -3
- package/dist/server/studio-ui/shell/pipeline-state.js +2 -11
- 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 +68 -0
- package/dist/server/studio-ui/shell/router.js +25 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +7 -5
- package/dist/server/studio-ui/shell/toasts-format.js +4 -4
- package/dist/server/studio-ui/shell/triage.js +1 -1
- package/dist/server/studio-ui/styles/console.js +36 -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 +23 -6
- 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 +11 -12
- package/plugin/agents/verifier.md +4 -1
- package/plugin/skills/nativ/SKILL.md +7 -7
- package/templates/AGENTS.md +7 -7
- package/templates/GEMINI.md +4 -3
- package/templates/claude-agents/architect.md +11 -12
- package/templates/claude-agents/verifier.md +4 -1
- 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 +8 -2
- 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
|
@@ -34,9 +34,9 @@ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic ben
|
|
|
34
34
|
if (env.cpuCount) meta.push(env.cpuCount + ' CPUs');
|
|
35
35
|
var score = clampPct(s.score);
|
|
36
36
|
return '<section class="card hero' + (running ? ' is-running' : '') + '" aria-labelledby="hero-h" aria-busy="' + running + '">' +
|
|
37
|
-
'<div><h2 class="
|
|
37
|
+
'<div><h2 class="hero-title" id="hero-h">Average throughput</h2>' +
|
|
38
38
|
'<div class="hero-value">' + num(Math.round(s.averageThroughputOpsPerSec || 0)) + '<span class="badge badge-accent">ops/sec</span></div>' +
|
|
39
|
-
'<p class="hero-meta">' + meta.join('
|
|
39
|
+
'<p class="hero-meta">' + meta.join(', ') + '</p></div>' +
|
|
40
40
|
'<dl class="hero-stats">' +
|
|
41
41
|
'<div><dt>Score</dt><dd class="' + (score < 100 ? 'text-danger' : '') + '">' + score + '%</dd></div>' +
|
|
42
42
|
'<div><dt>Scenarios passed</dt><dd>' + num(s.passedScenarios) + '/' + num(s.totalScenarios) + '</dd></div>' +
|
|
@@ -48,16 +48,17 @@ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic ben
|
|
|
48
48
|
function scenarioCards(r) {
|
|
49
49
|
var byId = {}, known = {};
|
|
50
50
|
(r.scenarios || []).forEach(function (s) { byId[s.id] = s; });
|
|
51
|
-
var cards =
|
|
51
|
+
var cards = [], idle = [];
|
|
52
|
+
SCENARIOS.forEach(function (meta) {
|
|
53
|
+
known[meta.id] = true;
|
|
54
|
+
if (byId[meta.id]) cards.push(scenarioCard(meta.label, byId[meta.id])); else idle.push(meta.label);
|
|
55
|
+
});
|
|
52
56
|
(r.scenarios || []).forEach(function (s) { if (!known[s.id]) cards.push(scenarioCard(s.name || s.id, s)); });
|
|
57
|
+
if (idle.length) cards.push('<p class="muted not-run">Not run: ' + idle.map(esc).join(', ') + '.</p>');
|
|
53
58
|
return cards.join('');
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
function scenarioCard(label, s) {
|
|
57
|
-
if (!s) {
|
|
58
|
-
return '<article class="card scenario is-idle"><header class="scenario-head"><h3>' + esc(label) + '</h3><span class="badge badge-neutral">Not run</span></header>' +
|
|
59
|
-
'<p class="muted">Not part of the last run. Run the full matrix to score it.</p></article>';
|
|
60
|
-
}
|
|
61
62
|
var total = (s.assertionsPassed || 0) + (s.assertionsFailed || 0);
|
|
62
63
|
var passed = s.status === 'passed';
|
|
63
64
|
var score = total ? Math.round((s.assertionsPassed || 0) / total * 100) : (passed ? 100 : 0);
|
|
@@ -76,7 +77,7 @@ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic ben
|
|
|
76
77
|
'<div class="metrics"><div><span class="metric-v">' + score + '%</span><span class="metric-l">Score</span></div>' +
|
|
77
78
|
'<div><span class="metric-v">' + fmtMs(s.durationMs) + '</span><span class="metric-l">Latency</span></div>' +
|
|
78
79
|
'<div><span class="metric-v">' + num(Math.round(s.throughputOpsPerSec || 0)) + '</span><span class="metric-l">ops/sec</span></div></div>' +
|
|
79
|
-
progressBar(score, 'sc-' + s.id, label + ' score', passed ? '
|
|
80
|
+
progressBar(score, 'sc-' + s.id, label + ' score', passed ? '' : 'danger') +
|
|
80
81
|
(details ? '<dl class="details">' + details + '</dl>' : '') + errors + '</article>';
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
// Benchmarks view styles.
|
|
2
2
|
export const benchmarksCss = String.raw `/* Benchmarks */
|
|
3
|
-
.hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 20px 32px; padding: 22px; margin-bottom:
|
|
3
|
+
.hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 20px 32px; padding: 22px 0; margin-bottom: 0; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; box-shadow: none; transition: opacity .2s; }
|
|
4
4
|
.hero.is-running { opacity: .6; }
|
|
5
|
-
.
|
|
5
|
+
.hero-title { margin: 0; font-size: 14px; font-weight: 600; color: var(--muted); }
|
|
6
|
+
.not-run { margin: 0; padding: 12px 0; font-size: 13px; }
|
|
7
|
+
.scenario .progress { height: 4px; }
|
|
6
8
|
.hero-value { display: flex; align-items: center; gap: 10px; margin: 6px 0; font-size: 40px; font-weight: 650; letter-spacing: -0.03em; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
7
9
|
.hero-value .badge { font-size: 12px; letter-spacing: 0; }
|
|
8
10
|
.hero-meta { margin: 0; font-size: 13.5px; color: var(--muted); }
|
|
9
11
|
.hero-stats { display: grid; grid-template-columns: repeat(4, minmax(96px, auto)); gap: 4px 28px; margin: 0; }
|
|
10
12
|
.hero-stats dt { font-size: 12.5px; color: var(--muted); }
|
|
11
13
|
.hero-stats dd { margin: 0; font-size: 18px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
12
|
-
.scenario-grid { display: grid; grid-template-columns:
|
|
13
|
-
.scenario { display: flex; flex-direction: column; gap: 10px; min-width: 0; padding:
|
|
14
|
-
.scenario
|
|
14
|
+
.scenario-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
|
|
15
|
+
.scenario { display: flex; flex-direction: column; gap: 10px; min-width: 0; padding: 12px 0; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; box-shadow: none; transition: background .15s; }
|
|
16
|
+
.scenario:hover { background: var(--surface-2); }
|
|
17
|
+
.scenario.is-idle { color: var(--fg-3); }
|
|
15
18
|
.scenario-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
16
19
|
.scenario-head h3 { margin: 0; font-size: 14.5px; font-weight: 600; }
|
|
17
20
|
.scenario-sub { margin: -6px 0 0; font-size: 13px; color: var(--muted); }
|
|
18
21
|
.scenario p { margin: 0; font-size: 13.5px; line-height: 1.5; }
|
|
19
|
-
.metrics { display:
|
|
22
|
+
.metrics { display: inline-flex; font-variant-numeric: tabular-nums; }
|
|
23
|
+
.metrics > * { padding: 0 16px; border-left: 1px solid var(--line); }
|
|
24
|
+
.metrics > :first-child { padding-left: 0; border-left: 0; }
|
|
20
25
|
.metric-v { display: block; font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
21
26
|
.metric-l { display: block; font-size: 12px; color: var(--muted); }
|
|
22
27
|
.details { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 12px; margin: 0; padding-top: 10px; font-size: 12.5px; border-top: 1px solid var(--border); }
|
|
@@ -116,7 +116,7 @@ export const databaseBrowserScript = String.raw ` // ─── Database Studio:
|
|
|
116
116
|
var start = res.totalCount === 0 ? 0 : (res.page - 1) * res.limit + 1;
|
|
117
117
|
var end = Math.min(res.page * res.limit, res.totalCount);
|
|
118
118
|
var pagination = '<div class="pagination-bar">' +
|
|
119
|
-
'<div>Showing ' + start + '
|
|
119
|
+
'<div>Showing ' + start + '-' + end + ' of ' + res.totalCount + ' ' + (isColl ? 'documents' : 'records') + '</div>' +
|
|
120
120
|
'<div class="pagination-ctrls">' +
|
|
121
121
|
'<button class="btn small" type="button" id="btn-page-prev"' + (res.page <= 1 ? ' disabled' : '') + '>Previous</button>' +
|
|
122
122
|
'<span>Page ' + res.page + ' of ' + Math.max(1, res.totalPages) + '</span>' +
|
|
@@ -168,7 +168,7 @@ export const databaseBrowserScript = String.raw ` // ─── Database Studio:
|
|
|
168
168
|
table.columns.forEach(function (col) {
|
|
169
169
|
var isPk = col.primaryKey;
|
|
170
170
|
var fieldLabel = esc(col.name) + (isPk ? ' (Primary Key - Optional/Auto)' : '');
|
|
171
|
-
var placeholder = col.type + (col.default ? '
|
|
171
|
+
var placeholder = col.type + (col.default ? ', default: ' + col.default : '');
|
|
172
172
|
var isLong = /json|map|array|text/i.test(col.type);
|
|
173
173
|
if (isLong) {
|
|
174
174
|
html += '<div class="field field-full"><label><b>' + fieldLabel + '</b></label><textarea class="input" name="' + esc(col.name) + '" placeholder="' + esc(placeholder) + '"></textarea></div>';
|
|
@@ -3,10 +3,11 @@ export const databaseConnectionScript = String.raw ` // ─── Database Stud
|
|
|
3
3
|
function renderModalStatus() {
|
|
4
4
|
['dev', 'prod'].forEach(function (env) {
|
|
5
5
|
var s = state.status && state.status[env];
|
|
6
|
-
|
|
6
|
+
var unset = !s || /No (Dev|Prod) database configured/.test(s.error || '');
|
|
7
|
+
$('mdot-' + env).className = 'dot ' + (s && s.connected ? 'on' : unset ? '' : 'off');
|
|
7
8
|
$('current-' + env).innerHTML = s && s.maskedUrl
|
|
8
9
|
? 'Current: <code>' + esc(s.maskedUrl) + '</code><span class="copy-wrap"><span class="tooltip">Copied!</span><button class="btn small" type="button" data-copy-masked="' + env + '">Copy</button></span>'
|
|
9
|
-
: 'Current: <span class="muted">
|
|
10
|
+
: 'Current: <span class="muted">Not set up</span>';
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -139,8 +140,8 @@ export const databaseConnectionScript = String.raw ` // ─── Database Stud
|
|
|
139
140
|
if (r.success) {
|
|
140
141
|
var count = r.entityCount != null ? r.entityCount : r.tableCount;
|
|
141
142
|
out.className = 'result ok';
|
|
142
|
-
out.textContent = 'Connected to ' + (ENGINE_LABEL[r.engine] || r.engine) + ' in ' + Math.round(r.pingMs) + 'ms
|
|
143
|
-
entityNoun({ engine: r.engine }, true).toLowerCase() + '
|
|
143
|
+
out.textContent = 'Connected to ' + (ENGINE_LABEL[r.engine] || r.engine) + ' in ' + Math.round(r.pingMs) + 'ms, ' + count + ' ' +
|
|
144
|
+
entityNoun({ engine: r.engine }, true).toLowerCase() + ', saved to session as ' + r.maskedUrl;
|
|
144
145
|
$('url-' + env).value = '';
|
|
145
146
|
$('ppass-' + env).value = '';
|
|
146
147
|
return true;
|
|
@@ -148,7 +149,7 @@ export const databaseConnectionScript = String.raw ` // ─── Database Stud
|
|
|
148
149
|
out.className = 'result err';
|
|
149
150
|
out.textContent = 'Error: ' + (r.error || 'Connection failed');
|
|
150
151
|
var hint = hintFor(r.error);
|
|
151
|
-
if (hint) out.textContent += '
|
|
152
|
+
if (hint) out.textContent += ': ' + hint;
|
|
152
153
|
return false;
|
|
153
154
|
}, function (e) {
|
|
154
155
|
out.className = 'result err'; out.textContent = 'Error: ' + e.message; return false;
|
|
@@ -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();
|
|
@@ -53,9 +53,12 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
53
53
|
var label = env.toUpperCase();
|
|
54
54
|
if (!s) return '<span class="pill"><span class="dot"></span><b>' + label + ':</b> loading…</span>';
|
|
55
55
|
if (!s.connected) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (/No (Dev|Prod) database configured/.test(s.error || '')) {
|
|
57
|
+
return '<span class="pill"><span class="dot"></span><b>' + label + ':</b> ' +
|
|
58
|
+
'<span class="badge badge-neutral">Not set up</span></span>';
|
|
59
|
+
}
|
|
60
|
+
return '<span class="pill" title="' + esc(s.error || '') + '"><span class="dot off"></span><b>' + label + ':</b> offline' +
|
|
61
|
+
' <span class="badge badge-offline">Offline</span></span>';
|
|
59
62
|
}
|
|
60
63
|
var sourceBadge = s.synthesized && s.sourceKey
|
|
61
64
|
? ' <span class="badge badge-synth" title="Assembled in memory from fragmented .env keys">synthesized from ' + esc(keyFamily(s.sourceKey)) + '</span>'
|
|
@@ -63,7 +66,7 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
63
66
|
? ' <span class="badge badge-source" title="Detected in ' + esc(s.exampleFile || '.env.example') + '">via ' + esc(s.sourceKey) + '</span>'
|
|
64
67
|
: (s.sourceKey ? ' <span class="badge badge-source">via ' + esc(s.sourceKey) + '</span>' : '');
|
|
65
68
|
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 ? '
|
|
69
|
+
esc(ENGINE_LABEL[s.engine] || s.engine) + (s.database ? ', ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms): ' +
|
|
67
70
|
entityCount(s) + ' ' + entityNoun(s, true) + sourceBadge + ' <span class="badge badge-online">ONLINE</span></span>';
|
|
68
71
|
}
|
|
69
72
|
|
|
@@ -97,15 +100,8 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
97
100
|
'<span class="diagnostic-tag">[DIAGNOSTIC]</span> ' +
|
|
98
101
|
'<span class="diagnostic-msg">' + esc(suggestion) + '</span>' +
|
|
99
102
|
'</div>' +
|
|
100
|
-
'<div class="diagnostic-actions">' +
|
|
101
|
-
'<button class="btn small" type="button" id="btn-diag-settings">Open Connection Settings</button>' +
|
|
102
|
-
'</div>' +
|
|
103
103
|
'</div>';
|
|
104
104
|
bannerEl.hidden = false;
|
|
105
|
-
var btnDiag = $('btn-diag-settings');
|
|
106
|
-
if (btnDiag) {
|
|
107
|
-
btnDiag.onclick = function () { openSettings(); };
|
|
108
|
-
}
|
|
109
105
|
} else {
|
|
110
106
|
bannerEl.innerHTML = '';
|
|
111
107
|
bannerEl.hidden = true;
|
|
@@ -119,7 +115,7 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
119
115
|
: ((state.schema[activeEnv + 'Tables'] || []).length);
|
|
120
116
|
$('count-data').textContent = String(dataCount);
|
|
121
117
|
var d = state.diff && state.diff.summary;
|
|
122
|
-
$('count-drift').textContent = d ? String(d.addedTablesCount + d.alteredTablesCount + d.droppedTablesCount) : '
|
|
118
|
+
$('count-drift').textContent = d ? String(d.addedTablesCount + d.alteredTablesCount + d.droppedTablesCount) : '-';
|
|
123
119
|
var script = state.diff ? generateScript(state.diff) : { text: '', lang: 'sql', count: 0 };
|
|
124
120
|
state.sql = script.text;
|
|
125
121
|
state.scriptLang = script.lang;
|
|
@@ -154,12 +150,11 @@ export const databaseDataScript = String.raw ` // ─── View 5: Database St
|
|
|
154
150
|
return '<div class="sr-only">Loading database schema…</div><div class="panes" aria-busy="true">' + col + col + '</div>';
|
|
155
151
|
}
|
|
156
152
|
|
|
157
|
-
var DB_ICON = '<svg class="icon empty-icon" width="56" height="56" viewBox="0 0
|
|
158
|
-
'<
|
|
153
|
+
var DB_ICON = '<svg class="icon empty-icon" width="56" height="56" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">' +
|
|
154
|
+
'<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
155
|
|
|
160
156
|
function emptyState(title, text) {
|
|
161
|
-
return '<div class="empty card">' + DB_ICON + '<h3>' + esc(title) + '</h3><p>' + esc(text) + '</p>'
|
|
162
|
-
'<button class="btn primary" type="button" data-action="open-settings">Connection Settings</button></div>';
|
|
157
|
+
return '<div class="empty card">' + DB_ICON + '<h3>' + esc(title) + '</h3><p>' + esc(text) + '</p></div>';
|
|
163
158
|
}
|
|
164
159
|
|
|
165
160
|
`;
|
|
@@ -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>' : '');
|
|
@@ -292,7 +292,7 @@ export const databaseDriftScript = String.raw ` // ─── Database Studio: d
|
|
|
292
292
|
var lang = state.scriptLang;
|
|
293
293
|
var unit = lang === 'json' ? ' change(s)' : ' statement(s)';
|
|
294
294
|
return '<div class="sql-wrap card"><div class="sql-head"><span class="muted"><span class="lang">' + LANG_LABEL[lang] + '</span>' +
|
|
295
|
-
(destructive ? '<span class="badge badge-dropped">DESTRUCTIVE</span> ' : '') + state.scriptCount + unit + '
|
|
295
|
+
(destructive ? '<span class="badge badge-dropped">DESTRUCTIVE</span> ' : '') + state.scriptCount + unit + ', read-only preview</span>' +
|
|
296
296
|
'<span class="copy-wrap"><span class="tooltip">Copied!</span><button class="btn small" type="button" data-action="copy-sql">Copy ' +
|
|
297
297
|
(lang === 'sql' ? 'SQL' : lang === 'js' ? 'Script' : 'JSON') + '</button></span></div>' +
|
|
298
298
|
'<pre class="sql" tabindex="0" aria-label="Migration script (' + LANG_LABEL[lang] + ')"><code>' + highlightScript(state.sql, lang) + '</code></pre></div>';
|
|
@@ -123,8 +123,7 @@ export const databaseEventsScript = String.raw ` // ─── Events: Database
|
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
var action = el.getAttribute('data-action');
|
|
126
|
-
if (action === '
|
|
127
|
-
else if (action === 'copy-sql') copyText(state.sql, el);
|
|
126
|
+
if (action === 'copy-sql') copyText(state.sql, el);
|
|
128
127
|
else if (action === 'edit-record') openEditRecordModal(Number(el.getAttribute('data-row')));
|
|
129
128
|
else if (action === 'delete-record') handleDeleteRecord(Number(el.getAttribute('data-row')));
|
|
130
129
|
else if (action === 'preview-url') {
|
|
@@ -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('');
|
|
@@ -45,7 +45,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
|
|
|
45
45
|
: '';
|
|
46
46
|
var docMeta = coll && (t.documentCount != null || t.sampledDocuments != null)
|
|
47
47
|
? '<p class="desc">' + (t.documentCount != null ? '~' + t.documentCount + ' documents' : 'Document count unavailable') +
|
|
48
|
-
(t.sampledDocuments != null ? '
|
|
48
|
+
(t.sampledDocuments != null ? ', fields inferred from ' + t.sampledDocuments + ' sampled' : '') + '</p>'
|
|
49
49
|
: '';
|
|
50
50
|
return '<details class="table-card"><summary><span class="tname">' + esc(t.name) + '</span>' +
|
|
51
51
|
(coll ? '<span class="badge badge-collection">COLLECTION</span>' : '') +
|
|
@@ -87,7 +87,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
|
|
|
87
87
|
var parts = [m.mimeType || 'unknown type'];
|
|
88
88
|
if (m.width && m.height) parts.push(m.width + '×' + m.height);
|
|
89
89
|
if (m.sizeBytes != null) parts.push(formatBytes(m.sizeBytes));
|
|
90
|
-
return parts.join('
|
|
90
|
+
return parts.join(', ');
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function showThumb(el) {
|
|
@@ -117,7 +117,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
|
|
|
117
117
|
hideThumb();
|
|
118
118
|
var m = entry.media;
|
|
119
119
|
$('media-title').textContent = entry.table + '.' + entry.field;
|
|
120
|
-
$('media-sub').textContent = (m ? SOURCE_LABEL[m.source] || m.source : 'Image field') + '
|
|
120
|
+
$('media-sub').textContent = (m ? SOURCE_LABEL[m.source] || m.source : 'Image field') + ', ' + entry.type;
|
|
121
121
|
$('media-thumb').innerHTML = thumbHtml(m);
|
|
122
122
|
var rows = [];
|
|
123
123
|
if (m) {
|
|
@@ -144,7 +144,7 @@ export const databaseExplorerScript = String.raw ` // ─── Database Studio
|
|
|
144
144
|
var body;
|
|
145
145
|
if (!s || !s.connected) {
|
|
146
146
|
body = '<div class="empty" style="padding:28px 12px"><p>' + esc(s && s.error ? s.error : 'No database connected.') + '</p>' +
|
|
147
|
-
'<
|
|
147
|
+
'<p class="muted">Use Connection Settings at the top.</p></div>';
|
|
148
148
|
} else if (!tables.length) {
|
|
149
149
|
body = '<p class="muted">Connected, but this database has no ' + entityNoun(s, true).toLowerCase() + ' yet.</p>';
|
|
150
150
|
} else if (!filtered.length) {
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
// Plan layout: rounds, lanes, links and the highlight set. Pure, no DOM.
|
|
2
|
-
export const flowLayoutScript = String.raw ` var FLOW = { W: 206, H:
|
|
2
|
+
export const flowLayoutScript = String.raw ` var FLOW = { W: 206, H: 74, GX: 52, GY: 10, PAD: 14, LX: 128, TOP: 30 };
|
|
3
3
|
|
|
4
4
|
function flowAgentKey(t) { return t.agent || 'unassigned'; }
|
|
5
5
|
|
|
@@ -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,14 +18,14 @@ 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)); }
|
|
25
25
|
#panel-flow .node .l1 { display: flex; align-items: center; gap: 6px; }
|
|
26
26
|
#panel-flow .node .l1 .d { margin-left: auto; font: 11px var(--mono); color: var(--fg-3); }
|
|
27
27
|
#panel-flow .node .l1 .mono { font-size: 11px; }
|
|
28
|
-
#panel-flow .node .title { font-size: 12.5px;
|
|
28
|
+
#panel-flow .node .title { font-size: 12.5px; line-height: 1.3; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
|
|
29
29
|
#panel-flow .canvas.focus .node:not(.hot) { opacity: .3; }
|
|
30
30
|
#panel-flow .node.sel { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
31
31
|
#panel-flow .legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--fg-2); align-items: center; }
|
|
@@ -51,7 +51,7 @@ export const flowTimelineScript = String.raw ` function flowStat(k, v, s) {
|
|
|
51
51
|
var cw = t.checkMs && t.status === 'completed' ? Math.min(w, t.checkMs * tm.sg.k) : 0;
|
|
52
52
|
var lx = w > 64 ? x1 + 6 : x2 + 4;
|
|
53
53
|
var took = (t.status === 'in_progress' ? 'running' : 'took ' + fmt.dur(t.durationMs)) + (t.checkMs ? ', check ' + fmt.dur(t.checkMs) : '');
|
|
54
|
-
var tip = ui.esc(t.id + '
|
|
54
|
+
var tip = ui.esc(t.id + ', ' + t.title) + ' ' + ui.esc(took);
|
|
55
55
|
return '<g><rect class="bar-w ' + ui.esc(t.status) + '" data-task="' + ui.esc(t.id) + '" data-row="' + row + '" x="' + x1 + '" y="' + by +
|
|
56
56
|
'" width="' + w + '" height="21" rx="4"><title>' + tip + '</title></rect>' +
|
|
57
57
|
(cw > 1 ? '<rect class="bar-c" x="' + (x2 - cw) + '" y="' + (by + 17) + '" width="' + cw + '" height="4" rx="1"/>' : '') +
|