@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +71 -1
  3. package/dist/commands/design-check.d.ts +4 -0
  4. package/dist/commands/design-check.js +76 -0
  5. package/dist/commands/init.d.ts +1 -0
  6. package/dist/commands/init.js +53 -1
  7. package/dist/commands/update.js +46 -0
  8. package/dist/core/design-rules.d.ts +23 -0
  9. package/dist/core/design-rules.js +115 -0
  10. package/dist/core/harness-integrity.js +26 -7
  11. package/dist/core/setup/pm-playbook.d.ts +1 -1
  12. package/dist/core/setup/pm-playbook.js +1 -1
  13. package/dist/index.js +11 -0
  14. package/dist/server/studio-ui/pages/benchmarks/script.js +9 -8
  15. package/dist/server/studio-ui/pages/benchmarks/styles.js +11 -6
  16. package/dist/server/studio-ui/pages/database/browser.js +2 -2
  17. package/dist/server/studio-ui/pages/database/connection.js +6 -5
  18. package/dist/server/studio-ui/pages/database/data-shell.js +12 -17
  19. package/dist/server/studio-ui/pages/database/drift.js +2 -2
  20. package/dist/server/studio-ui/pages/database/events.js +1 -2
  21. package/dist/server/studio-ui/pages/database/explorer.js +6 -6
  22. package/dist/server/studio-ui/pages/database/styles.js +5 -5
  23. package/dist/server/studio-ui/pages/flow/layout.js +1 -1
  24. package/dist/server/studio-ui/pages/flow/styles.js +4 -4
  25. package/dist/server/studio-ui/pages/flow/timeline-view.js +1 -1
  26. package/dist/server/studio-ui/pages/home/script.js +108 -92
  27. package/dist/server/studio-ui/pages/home/styles.js +29 -45
  28. package/dist/server/studio-ui/pages/tasks/script.js +146 -14
  29. package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
  30. package/dist/server/studio-ui/pages/team/script.js +69 -53
  31. package/dist/server/studio-ui/pages/team/styles.js +20 -20
  32. package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
  33. package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
  34. package/dist/server/studio-ui/shell/brand.d.ts +4 -0
  35. package/dist/server/studio-ui/shell/brand.js +16 -0
  36. package/dist/server/studio-ui/shell/console.js +71 -6
  37. package/dist/server/studio-ui/shell/core.js +8 -8
  38. package/dist/server/studio-ui/shell/detail-view.js +4 -4
  39. package/dist/server/studio-ui/shell/dispatch.js +23 -4
  40. package/dist/server/studio-ui/shell/events-tools.js +16 -1
  41. package/dist/server/studio-ui/shell/format.js +36 -8
  42. package/dist/server/studio-ui/shell/icons.js +23 -15
  43. package/dist/server/studio-ui/shell/markup-head.js +11 -6
  44. package/dist/server/studio-ui/shell/markup-tools.js +29 -15
  45. package/dist/server/studio-ui/shell/model.js +8 -2
  46. package/dist/server/studio-ui/shell/paint.js +4 -3
  47. package/dist/server/studio-ui/shell/pipeline-state.js +2 -11
  48. package/dist/server/studio-ui/shell/proposals.js +6 -2
  49. package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
  50. package/dist/server/studio-ui/shell/pulse.js +68 -0
  51. package/dist/server/studio-ui/shell/router.js +25 -0
  52. package/dist/server/studio-ui/shell/sidebar-view.js +7 -5
  53. package/dist/server/studio-ui/shell/toasts-format.js +4 -4
  54. package/dist/server/studio-ui/shell/triage.js +1 -1
  55. package/dist/server/studio-ui/styles/console.js +36 -13
  56. package/dist/server/studio-ui/styles/dialogs.js +4 -4
  57. package/dist/server/studio-ui/styles/responsive.js +8 -0
  58. package/dist/server/studio-ui/styles/shell.js +23 -6
  59. package/dist/server/studio-ui/styles/strategist.js +10 -10
  60. package/dist/server/studio-ui/styles/tokens-base.js +23 -23
  61. package/dist/server/studio-ui.js +2 -0
  62. package/package.json +1 -1
  63. package/plugin/.claude-plugin/plugin.json +1 -1
  64. package/plugin/agents/architect.md +11 -12
  65. package/plugin/agents/verifier.md +4 -1
  66. package/plugin/skills/nativ/SKILL.md +7 -7
  67. package/templates/AGENTS.md +7 -7
  68. package/templates/GEMINI.md +4 -3
  69. package/templates/claude-agents/architect.md +11 -12
  70. package/templates/claude-agents/verifier.md +4 -1
  71. package/templates/design/taste/LICENSE +21 -0
  72. package/templates/design/taste/SOURCE.md +28 -0
  73. package/templates/design/taste/v1/DIGEST.md +57 -0
  74. package/templates/design/taste/v1/SKILL.md +226 -0
  75. package/templates/design/taste/v2/DIGEST.md +62 -0
  76. package/templates/design/taste/v2/SKILL.md +1206 -0
  77. package/templates/dot-ai/subagents/architect.md +8 -2
  78. package/templates/dot-ai/subagents/flutter-developer.md +2 -11
  79. package/templates/dot-ai/subagents/frontend.md +47 -44
  80. 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="eyebrow" id="hero-h">Average throughput</h2>' +
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(' · ') + '</p></div>' +
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 = SCENARIOS.map(function (meta) { known[meta.id] = true; return scenarioCard(meta.label, byId[meta.id]); });
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 ? 'success' : 'danger') +
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: 16px; transition: opacity .2s; }
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
- .eyebrow { margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
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: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
13
- .scenario { display: flex; flex-direction: column; gap: 10px; min-width: 0; padding: 16px; }
14
- .scenario.is-idle { box-shadow: none; border-style: dashed; }
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: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
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 + '–' + end + ' of ' + res.totalCount + ' ' + (isColl ? 'documents' : 'records') + '</div>' +
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 ? ' · default: ' + 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
- $('mdot-' + env).className = 'dot ' + (s && s.connected ? 'on' : 'off');
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">not configured</span>';
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 · ' + count + ' ' +
143
- entityNoun({ engine: r.engine }, true).toLowerCase() + ' · saved to session as ' + r.maskedUrl;
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 += ' — ' + hint;
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 — data loading ────────────────────────────────
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
- var why = /No (Dev|Prod) database configured/.test(s.error || '') ? 'not configured' : 'offline';
57
- return '<span class="pill" title="' + esc(s.error || '') + '"><span class="dot off"></span><b>' + label + ':</b> ' + why +
58
- ' <span class="badge badge-offline">OFFLINE</span></span>';
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 ? ' · ' + esc(s.database) : '') + ' (' + Math.round(s.pingMs) + 'ms) – ' +
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 24 24" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">' +
158
- '<ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 11v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/></svg>';
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">—</span>';
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 + ' · read-only preview</span>' +
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 === 'open-settings') openSettings();
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">–</span>')
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">–</span>') + '</td><td class="mono">' + esc(c.name) +
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 ? ' · fields inferred from ' + t.sampledDocuments + ' sampled' : '') + '</p>'
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') + ' · ' + entry.type;
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
- '<button class="btn small" type="button" data-action="open-settings">Connect</button></div>';
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: 4px; font-family: var(--mono); }
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-float); pointer-events: none; }
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: #fff; 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; }
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: 5px; cursor: pointer; color: var(--muted); font-size: 13px; font-weight: 600; transition: all .15s ease; }
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: 4px; border: 1px solid var(--primary-line); color: var(--primary); background: var(--primary-tint); cursor: pointer; }
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: 58, GX: 52, GY: 10, PAD: 14, LX: 128, TOP: 30 };
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: 8px; overflow: hidden; background: var(--surface); }
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: 12px; min-width: 0; overflow: hidden; }
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: 9px; text-align: left; cursor: pointer; transition: opacity .15s, border-color .15s, box-shadow .15s; z-index: 1; }
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; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
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 + ' · ' + t.title) + '&#10;' + ui.esc(took);
54
+ var tip = ui.esc(t.id + ', ' + t.title) + '&#10;' + 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"/>' : '') +