@njeybe/nativ 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +17 -0
  3. package/dist/server/studio-ui/document.d.ts +3 -0
  4. package/dist/server/studio-ui/document.js +22 -0
  5. package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
  6. package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
  7. package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
  8. package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
  9. package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
  10. package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
  11. package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
  12. package/dist/server/studio-ui/pages/database/browser.js +326 -0
  13. package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
  14. package/dist/server/studio-ui/pages/database/connection.js +158 -0
  15. package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
  16. package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
  17. package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
  18. package/dist/server/studio-ui/pages/database/drift.js +301 -0
  19. package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
  20. package/dist/server/studio-ui/pages/database/events.js +231 -0
  21. package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
  22. package/dist/server/studio-ui/pages/database/explorer.js +183 -0
  23. package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
  24. package/dist/server/studio-ui/pages/database/index.js +28 -0
  25. package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
  26. package/dist/server/studio-ui/pages/database/markup.js +32 -0
  27. package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
  28. package/dist/server/studio-ui/pages/database/state.js +57 -0
  29. package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
  30. package/dist/server/studio-ui/pages/database/styles.js +118 -0
  31. package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
  32. package/dist/server/studio-ui/pages/flow/index.js +11 -0
  33. package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
  34. package/dist/server/studio-ui/pages/flow/layout.js +105 -0
  35. package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
  36. package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
  37. package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
  38. package/dist/server/studio-ui/pages/flow/script.js +61 -0
  39. package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
  40. package/dist/server/studio-ui/pages/flow/styles.js +59 -0
  41. package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
  42. package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
  43. package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
  44. package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
  45. package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
  46. package/dist/server/studio-ui/pages/home/index.js +11 -0
  47. package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
  48. package/dist/server/studio-ui/pages/home/script.js +160 -0
  49. package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
  50. package/dist/server/studio-ui/pages/home/styles.js +48 -0
  51. package/dist/server/studio-ui/pages/index.d.ts +2 -0
  52. package/dist/server/studio-ui/pages/index.js +17 -0
  53. package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
  54. package/dist/server/studio-ui/pages/tasks/index.js +11 -0
  55. package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
  56. package/dist/server/studio-ui/pages/tasks/script.js +96 -0
  57. package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
  58. package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
  59. package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
  60. package/dist/server/studio-ui/pages/team/index.js +11 -0
  61. package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
  62. package/dist/server/studio-ui/pages/team/script.js +112 -0
  63. package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
  64. package/dist/server/studio-ui/pages/team/styles.js +22 -0
  65. package/dist/server/studio-ui/pages/types.d.ts +13 -0
  66. package/dist/server/studio-ui/pages/types.js +1 -0
  67. package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
  68. package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
  69. package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
  70. package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
  71. package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
  72. package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
  73. package/dist/server/studio-ui/shell/actions.d.ts +1 -0
  74. package/dist/server/studio-ui/shell/actions.js +49 -0
  75. package/dist/server/studio-ui/shell/boot.d.ts +2 -0
  76. package/dist/server/studio-ui/shell/boot.js +13 -0
  77. package/dist/server/studio-ui/shell/console.d.ts +1 -0
  78. package/dist/server/studio-ui/shell/console.js +194 -0
  79. package/dist/server/studio-ui/shell/core.d.ts +1 -0
  80. package/dist/server/studio-ui/shell/core.js +50 -0
  81. package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
  82. package/dist/server/studio-ui/shell/detail-view.js +80 -0
  83. package/dist/server/studio-ui/shell/detail.d.ts +1 -0
  84. package/dist/server/studio-ui/shell/detail.js +83 -0
  85. package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
  86. package/dist/server/studio-ui/shell/dispatch.js +156 -0
  87. package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
  88. package/dist/server/studio-ui/shell/events-tools.js +94 -0
  89. package/dist/server/studio-ui/shell/format.d.ts +1 -0
  90. package/dist/server/studio-ui/shell/format.js +151 -0
  91. package/dist/server/studio-ui/shell/icons.d.ts +2 -0
  92. package/dist/server/studio-ui/shell/icons.js +20 -0
  93. package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
  94. package/dist/server/studio-ui/shell/markup-head.js +43 -0
  95. package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
  96. package/dist/server/studio-ui/shell/markup-panels.js +7 -0
  97. package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
  98. package/dist/server/studio-ui/shell/markup-tools.js +222 -0
  99. package/dist/server/studio-ui/shell/model.d.ts +1 -0
  100. package/dist/server/studio-ui/shell/model.js +192 -0
  101. package/dist/server/studio-ui/shell/paint.d.ts +1 -0
  102. package/dist/server/studio-ui/shell/paint.js +154 -0
  103. package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
  104. package/dist/server/studio-ui/shell/palette-view.js +60 -0
  105. package/dist/server/studio-ui/shell/palette.d.ts +1 -0
  106. package/dist/server/studio-ui/shell/palette.js +80 -0
  107. package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
  108. package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
  109. package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
  110. package/dist/server/studio-ui/shell/proposals.js +197 -0
  111. package/dist/server/studio-ui/shell/pure.d.ts +3 -0
  112. package/dist/server/studio-ui/shell/pure.js +9 -0
  113. package/dist/server/studio-ui/shell/router.d.ts +1 -0
  114. package/dist/server/studio-ui/shell/router.js +95 -0
  115. package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
  116. package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
  117. package/dist/server/studio-ui/shell/sync.d.ts +1 -0
  118. package/dist/server/studio-ui/shell/sync.js +120 -0
  119. package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
  120. package/dist/server/studio-ui/shell/toasts-format.js +89 -0
  121. package/dist/server/studio-ui/shell/triage.d.ts +1 -0
  122. package/dist/server/studio-ui/shell/triage.js +210 -0
  123. package/dist/server/studio-ui/styles/console.d.ts +1 -0
  124. package/dist/server/studio-ui/styles/console.js +83 -0
  125. package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
  126. package/dist/server/studio-ui/styles/dialogs.js +118 -0
  127. package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
  128. package/dist/server/studio-ui/styles/responsive.js +28 -0
  129. package/dist/server/studio-ui/styles/shell.d.ts +1 -0
  130. package/dist/server/studio-ui/styles/shell.js +117 -0
  131. package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
  132. package/dist/server/studio-ui/styles/strategist.js +77 -0
  133. package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
  134. package/dist/server/studio-ui/styles/tokens-base.js +131 -0
  135. package/dist/server/studio-ui.d.ts +1 -10
  136. package/dist/server/studio-ui.js +63 -5314
  137. package/package.json +2 -2
  138. package/plugin/.claude-plugin/plugin.json +1 -1
  139. package/plugin/agents/architect.md +9 -5
  140. package/templates/GEMINI.md +12 -8
  141. package/templates/claude-agents/architect.md +9 -5
  142. package/templates/dot-ai/subagents/flutter-developer.md +10 -0
  143. package/templates/dot-ai/subagents/frontend.md +10 -0
  144. package/templates/dot-ai/ui_specs.md +36 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0
4
+
5
+ ### Changed
6
+
7
+ - **Studio redesign.** New Home, Tasks, Flow and Team pages focused on what needs you, what
8
+ finished, how the work connects, and how each agent is doing. Ctrl+K command palette to find
9
+ any task, a detail panel to read and act on one task, and light and dark themes.
10
+
11
+ ### Removed
12
+
13
+ - Agent Cockpits and Workflow Canvas pages and the simulation controls in the UI. The
14
+ simulation server endpoints remain for now and will be removed in a later cleanup.
15
+
16
+ ## 2.2.0
17
+
18
+ ### Added
19
+
20
+ - **Dual-Track Intake (Experience-First vs Data-First).** The `ui_specs.md` template can now start from either user experience (sketches and flows) or data structure (tables and entity relationships). Both paths lead to the same component map and style tile.
21
+ - **Semantic Component Trees.** Components now organize into a hierarchical structure with semantic roles: layout containers, data display, input controls, feedback, and navigation. The component map in `ui_specs.md` reflects these roles.
22
+ - **Stack-Aware Style Tiles.** Style tiles now include platform-specific variants. A web style tile shows how a design token adapts across desktop, tablet and mobile breakpoints. Flutter tiles show platform-native adaptations for iOS, Android and Cupertino.
23
+ - **Contextual Typography.** The `ui_specs.md` template documents font scales in context: scale per platform, role (heading, body, caption, code), and how type tokens map to semantic roles in components. Font sizes, weights and line heights are specified per context, not globally.
24
+ - **Mutation State Specs.** Every input component, button, and interactive element now carries a spec for its mutation states: default, hover, focus, disabled, loading, error and success. States are shown in the wireframe notes and in the component section.
25
+
3
26
  ## 2.1.0
4
27
 
5
28
  ### Added
@@ -24,7 +47,7 @@
24
47
 
25
48
  Documentation and package metadata only; no behaviour changes.
26
49
 
27
- - **npm is the only install path.** The source repository is private, so the instructions for `npm install -g github:…` and for adding the plugin marketplace were removed from the README and the installation guide. `nativ setup` is the complete route: it writes the agents, the enforcement hook, the MCP entry and the permission rules, which a plugin cannot ship.
50
+ - **npm is the only install path.** The source repository is private, so the instructions for `npm install -g github:...` and for adding the plugin marketplace were removed from the README and the installation guide. `nativ setup` is the complete route: it writes the agents, the enforcement hook, the MCP entry and the permission rules, which a plugin cannot ship.
28
51
  - The plugin files still ship inside the package (`plugin/`) and can be tried for one session with `claude --plugin-dir`.
29
52
  - `package.json` no longer declares `repository`, `homepage` or `bugs`, so the npm page has no dead links.
30
53
  - The 2.0.0 entry below still mentions the plugin marketplace; that route is not available outside the private repository.
package/README.md CHANGED
@@ -129,6 +129,8 @@ Start in `warn`, look at the log, then switch to `block`. If a task genuinely ne
129
129
 
130
130
  **Planning levels.** The `.ai/ui_specs.md` template opens with a planning level: Quick (small fix, no design phase), Standard (new screen in an existing design) or Full (new app or redesign). It then walks through the design brief, users and top tasks, real content samples, user flows, design directions with a style tile, wireframes, a component map and one section per component. The architect and frontend/flutter guides were trimmed to match.
131
131
 
132
+ **UI/UX design enhancements.** The template supports two intake approaches: Experience-First (start with user flows and wireframes) or Data-First (start with entity relationships and table schemas), both leading to the same component map. The component map now organizes components into semantic trees reflecting their roles in the interface (layout, data display, input, feedback, navigation). Style tiles are stack-aware, showing platform-specific token variants for web breakpoints and Flutter platform adaptations. Typography is specified in context with font scales per platform and semantic role, mapped directly to components. Every interactive element and input carries a mutation state spec: default, hover, focus, disabled, loading, error and success states, documented in wireframe notes and component sections.
133
+
132
134
  **Optional task fields.** A task can carry `specRefs` (anchors such as `ui_specs.md#appointment-list`), `complexity` (`simple`, `standard` or `complex`) and `acceptanceCriteria` ("done when" lines):
133
135
 
134
136
  ```bash
@@ -232,6 +234,21 @@ nativ bench --scenario concurrency --concurrency 8
232
234
  nativ bench --json
233
235
  ```
234
236
 
237
+ ### Studio
238
+
239
+ `nativ studio` runs a local dashboard at http://localhost:4983 with live updates. It loads
240
+ nothing from the internet and runs under a strict content security policy. The sidebar
241
+ groups pages by role.
242
+
243
+ **Home** shows what needs you, what's running now, and what just finished.
244
+ **Tasks** displays one milestone at a time, sorted newest completed first.
245
+ **Flow** shows how the work connects and visualizes how the run really happened.
246
+ **Team** lists each agent's activity in plain terms: tasks done, time spent, cost.
247
+ **Worktrees, Benchmarks, Database** are system tools for branches, performance and schemas.
248
+
249
+ Press Ctrl+K (or Cmd+K or /) to find any task by id, title, agent or file name. The theme
250
+ toggle switches between light and dark; your choice is saved locally.
251
+
235
252
  ### Database studio and schema telemetry (masked, structure only)
236
253
 
237
254
  ```bash
@@ -0,0 +1,3 @@
1
+ export declare function escapeHtml(value: string): string;
2
+ export declare const documentStart: string;
3
+ export declare const styleEnd = "</style>\n";
@@ -0,0 +1,22 @@
1
+ export function escapeHtml(value) {
2
+ return value.replace(/[&<>"']/g, (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch]);
3
+ }
4
+ // Runs before first paint so a saved theme never flashes the wrong colours.
5
+ const themeScript = String.raw `(function () {
6
+ try {
7
+ var t = localStorage.getItem('nativ-studio:theme');
8
+ if (t === 'dark' || t === 'light') document.documentElement.setAttribute('data-theme', t);
9
+ } catch (e) { /* storage blocked */ }
10
+ })();`;
11
+ // Opening of the document up to the style block; the CSS chunks follow it.
12
+ export const documentStart = String.raw `<!doctype html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1">
17
+ <meta name="color-scheme" content="light dark">
18
+ <title>Nativ Studio</title>
19
+ <script>${themeScript}</script>
20
+ <style>
21
+ `;
22
+ export const styleEnd = '</style>\n';
@@ -0,0 +1,2 @@
1
+ import type { PageModule } from '../types.js';
2
+ export declare const benchmarksPage: PageModule;
@@ -0,0 +1,11 @@
1
+ import { ICON_PATHS } from '../../shell/icons.js';
2
+ import { benchmarksCss } from './styles.js';
3
+ import { benchmarksScript } from './script.js';
4
+ export const benchmarksPage = {
5
+ id: 'benchmarks',
6
+ group: 'system',
7
+ label: 'Benchmarks',
8
+ icon: ICON_PATHS.benchmarks,
9
+ css: benchmarksCss,
10
+ script: benchmarksScript,
11
+ };
@@ -0,0 +1 @@
1
+ export declare const benchmarksScript: string;
@@ -0,0 +1,104 @@
1
+ // Benchmarks view script.
2
+ export const benchmarksScript = String.raw ` // ─── View 4: Synthetic benchmarks ──────────────────────────────────────────
3
+ var SCENARIOS = [
4
+ { id: 'concurrency', label: 'Multi-Agent Concurrency' },
5
+ { id: 'telemetry', label: 'Telemetry Engine' },
6
+ { id: 'governor', label: 'Governor Invariants' },
7
+ { id: 'verification', label: 'Gatekeeper' },
8
+ { id: 'e2e_pipeline', label: 'E2E Lifecycle' }
9
+ ];
10
+
11
+ function renderBenchmarks() {
12
+ var running = !!pipe.busy.bench;
13
+ var runBtn = '<button class="btn primary" type="button" data-act="bench-run"' + (running ? ' disabled' : '') + '>' +
14
+ (running ? '<span class="spinner" aria-hidden="true"></span>Running…' : ICON.play + 'Run Benchmark') + '</button>';
15
+ var head = panelHead('Synthetic Benchmarks', 'Sandboxed stress runs of plan locking, telemetry, the contract governor, the verification gatekeeper and the full agent lifecycle.', runBtn);
16
+ if (!pipe.loaded.benchmarks) return head + '<div class="card section" aria-hidden="true"><div class="skeleton tall"></div></div><div class="scenario-grid" aria-hidden="true">' + skeletonCards(5, 'scenario') + '</div>';
17
+ if (pipe.errors.benchmarks && !pipe.report) return head + errorCard('benchmark report', pipe.errors.benchmarks, 'benchmarks');
18
+ var r = pipe.report;
19
+ if (!r) {
20
+ return head + '<div class="card state" aria-busy="' + running + '"><div class="state-icon">' + (running ? '<span class="spinner" aria-hidden="true"></span>' : ICON.gauge) + '</div>' +
21
+ '<h2>' + (running ? 'Running benchmark matrix…' : 'No benchmark report yet') + '</h2>' +
22
+ '<p>' + (running ? 'Scenarios run in throwaway sandboxes; this usually takes a few seconds.' : 'Run the synthetic matrix to measure throughput and score all five pipeline scenarios. Results are cached in .ai/benchmark_report.json.') + '</p></div>';
23
+ }
24
+ return head + staleNote('benchmarks') + benchHero(r, running) + '<div class="scenario-grid">' + scenarioCards(r) + '</div>';
25
+ }
26
+
27
+ function benchHero(r, running) {
28
+ var s = r.summary || {};
29
+ var env = r.environment || {};
30
+ var meta = [];
31
+ if (r.timestamp) meta.push('Ran ' + esc(fmtDate(r.timestamp)));
32
+ if (env.platform) meta.push(esc(env.platform + (env.arch ? ' ' + env.arch : '')));
33
+ if (env.nodeVersion) meta.push('Node ' + esc(env.nodeVersion));
34
+ if (env.cpuCount) meta.push(env.cpuCount + ' CPUs');
35
+ var score = clampPct(s.score);
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>' +
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>' +
40
+ '<dl class="hero-stats">' +
41
+ '<div><dt>Score</dt><dd class="' + (score < 100 ? 'text-danger' : '') + '">' + score + '%</dd></div>' +
42
+ '<div><dt>Scenarios passed</dt><dd>' + num(s.passedScenarios) + '/' + num(s.totalScenarios) + '</dd></div>' +
43
+ '<div><dt>Total duration</dt><dd>' + fmtMs(s.totalDurationMs) + '</dd></div>' +
44
+ '<div><dt>Operations</dt><dd>' + num(s.totalOperations) + '</dd></div>' +
45
+ '</dl></section>';
46
+ }
47
+
48
+ function scenarioCards(r) {
49
+ var byId = {}, known = {};
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]); });
52
+ (r.scenarios || []).forEach(function (s) { if (!known[s.id]) cards.push(scenarioCard(s.name || s.id, s)); });
53
+ return cards.join('');
54
+ }
55
+
56
+ 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
+ var total = (s.assertionsPassed || 0) + (s.assertionsFailed || 0);
62
+ var passed = s.status === 'passed';
63
+ var score = total ? Math.round((s.assertionsPassed || 0) / total * 100) : (passed ? 100 : 0);
64
+ var details = Object.keys(s.details || {}).slice(0, 4).map(function (k) {
65
+ var v = s.details[k];
66
+ // Lists (e.g. invariant names) get a full-width row of chips instead of a cramped value column.
67
+ if (Array.isArray(v)) {
68
+ return '<dt class="wide">' + esc(humanize(k)) + '</dt><dd class="wide">' + v.map(function (item) { return '<span class="chip">' + esc(detailValue(item)) + '</span>'; }).join('') + '</dd>';
69
+ }
70
+ return '<dt>' + esc(humanize(k)) + '</dt><dd>' + esc(detailValue(v)) + '</dd>';
71
+ }).join('');
72
+ var errors = (s.errors || []).length ? '<ul class="scenario-errors">' + s.errors.map(function (e) { return '<li>' + esc(e) + '</li>'; }).join('') + '</ul>' : '';
73
+ return '<article class="card scenario"><header class="scenario-head"><h3>' + esc(label) + '</h3>' +
74
+ '<span class="badge badge-' + (passed ? 'success' : 'danger') + '">' + (passed ? 'Passed' : 'Failed') + '</span></header>' +
75
+ (s.name && s.name !== label ? '<p class="scenario-sub">' + esc(s.name) + '</p>' : '') +
76
+ '<div class="metrics"><div><span class="metric-v">' + score + '%</span><span class="metric-l">Score</span></div>' +
77
+ '<div><span class="metric-v">' + fmtMs(s.durationMs) + '</span><span class="metric-l">Latency</span></div>' +
78
+ '<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
+ (details ? '<dl class="details">' + details + '</dl>' : '') + errors + '</article>';
81
+ }
82
+
83
+ function runBenchmark() {
84
+ if (pipe.busy.bench) return;
85
+ pipe.busy.bench = true;
86
+ repaint('benchmarks');
87
+ postJson('/api/pipeline/benchmarks/run', {}).then(function (r) {
88
+ if (r.report) { pipe.report = r.report; pipe.errors.benchmarks = null; pipe.loaded.benchmarks = true; }
89
+ var s = r.report && r.report.summary;
90
+ toast('Benchmark finished' + (s ? ': score ' + clampPct(s.score) + '%, ' + s.passedScenarios + '/' + s.totalScenarios + ' scenarios passed' : ''), 'ok');
91
+ }, function (e) {
92
+ toast('Benchmark run failed: ' + e.message);
93
+ }).then(function () {
94
+ pipe.busy.bench = false;
95
+ repaint('benchmarks');
96
+ });
97
+ }
98
+
99
+ Studio.registerPage('benchmarks', {
100
+ deps: ['benchmarks'],
101
+ render: function () { return renderBenchmarks(); },
102
+ actions: { 'bench-run': function () { runBenchmark(); } }
103
+ });
104
+ `;
@@ -0,0 +1 @@
1
+ export declare const benchmarksCss: string;
@@ -0,0 +1,30 @@
1
+ // Benchmarks view styles.
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; }
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); }
6
+ .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
+ .hero-value .badge { font-size: 12px; letter-spacing: 0; }
8
+ .hero-meta { margin: 0; font-size: 13.5px; color: var(--muted); }
9
+ .hero-stats { display: grid; grid-template-columns: repeat(4, minmax(96px, auto)); gap: 4px 28px; margin: 0; }
10
+ .hero-stats dt { font-size: 12.5px; color: var(--muted); }
11
+ .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; }
15
+ .scenario-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
16
+ .scenario-head h3 { margin: 0; font-size: 14.5px; font-weight: 600; }
17
+ .scenario-sub { margin: -6px 0 0; font-size: 13px; color: var(--muted); }
18
+ .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; }
20
+ .metric-v { display: block; font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
21
+ .metric-l { display: block; font-size: 12px; color: var(--muted); }
22
+ .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); }
23
+ .details dt { color: var(--muted); }
24
+ .details dd { margin: 0; text-align: right; font-family: var(--mono); font-size: 12px; overflow-wrap: anywhere; }
25
+ .details .wide { grid-column: 1 / -1; }
26
+ .details dd.wide { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; text-align: left; }
27
+ .details dd.wide .chip { font-size: 11px; }
28
+ .scenario-errors { margin: 0; padding: 8px 10px 8px 26px; font-size: 12.5px; color: var(--danger); background: var(--danger-bg); border: 1px solid var(--danger-line); border-radius: var(--r-control); }
29
+
30
+ `;
@@ -0,0 +1 @@
1
+ export declare const databaseBrowserScript: string;
@@ -0,0 +1,326 @@
1
+ // Database Studio live data browser.
2
+ export const databaseBrowserScript = String.raw ` // ─── Database Studio: live data browser ────────────────────────────────────
3
+ function renderDataCell(val, colName) {
4
+ if (val === null || val === undefined) return '<td class="mono muted">NULL</td>';
5
+ if (typeof val === 'boolean') return '<td class="mono"><b>' + (val ? 'TRUE' : 'FALSE') + '</b></td>';
6
+ if (typeof val === 'string') {
7
+ var isImgUrl = /^https?:\/\//i.test(val) && /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)/i.test(val);
8
+ var isDataImg = /^data:image\//i.test(val);
9
+ if (isImgUrl || isDataImg) {
10
+ return '<td><button type="button" class="cell-preview-btn" data-action="preview-url" data-url="' + esc(val) + '">Preview Image</button> <span class="mono muted" style="font-size:11px">' + esc(val.length > 28 ? val.slice(0, 28) + '…' : val) + '</span></td>';
11
+ }
12
+ var displayStr = val.length > 50 ? val.slice(0, 50) + '…' : val;
13
+ return '<td class="mono" title="' + esc(val) + '">' + esc(displayStr) + '</td>';
14
+ }
15
+ if (typeof val === 'object') {
16
+ var json = JSON.stringify(val);
17
+ var display = json.length > 40 ? json.slice(0, 40) + '…' : json;
18
+ return '<td class="mono cell-expandable" title="Click to expand JSON" data-action="view-json" data-raw="' + esc(json) + '">' + esc(display) + '</td>';
19
+ }
20
+ return '<td class="mono">' + esc(String(val)) + '</td>';
21
+ }
22
+
23
+ function renderData() {
24
+ var activeEnv = state.envMode === 'prod' ? 'prod' : 'dev';
25
+ state.dataEnv = activeEnv;
26
+ var s = state.status && state.status[activeEnv];
27
+ if (!s || !s.connected) {
28
+ return emptyState((activeEnv === 'dev' ? 'Dev / Staging' : 'Production') + ' database not connected',
29
+ 'Please connect the ' + (activeEnv === 'dev' ? 'Dev' : 'Prod') + ' database in Connection Settings to browse and edit live data.');
30
+ }
31
+
32
+ var tables = state.schema[activeEnv + 'Tables'] || [];
33
+ if (!tables.length) {
34
+ return '<div class="empty card"><p>No ' + entityNoun(s, true).toLowerCase() + ' found in this database.</p></div>';
35
+ }
36
+
37
+ if (!state.dataEntity || !tables.some(function (t) { return t.name === state.dataEntity; })) {
38
+ state.dataEntity = tables[0].name;
39
+ }
40
+
41
+ var currentTable = tables.find(function (t) { return t.name === state.dataEntity; }) || tables[0];
42
+ var isColl = isCollection(currentTable);
43
+
44
+ var entityOptions = tables.map(function (t) {
45
+ var sel = t.name === state.dataEntity ? ' selected' : '';
46
+ return '<option value="' + esc(t.name) + '"' + sel + '>' + esc(t.name) + (isCollection(t) ? ' (collection)' : '') + '</option>';
47
+ }).join('');
48
+
49
+ var limitOptions = [25, 50, 100].map(function (n) {
50
+ var sel = state.dataLimit === n ? ' selected' : '';
51
+ return '<option value="' + n + '"' + sel + '>' + n + ' / page</option>';
52
+ }).join('');
53
+
54
+ var prodBannerHtml = activeEnv === 'prod'
55
+ ? '<div class="prod-banner"><span><b>Production Safeguard Active:</b> Operating on live production data. All mutations require strict challenge phrase confirmation and are recorded to .nativ/prod_audit.log.</span><span class="badge badge-prod">PROD</span></div>'
56
+ : '';
57
+
58
+ var toolbar = '<div class="data-toolbar">' +
59
+ '<div class="data-controls">' +
60
+ '<select class="input" id="data-entity-select" aria-label="Table or collection" style="min-width:180px;font-weight:600">' + entityOptions + '</select>' +
61
+ '<input class="input" id="data-search" type="search" aria-label="Search records" placeholder="Search records in ' + esc(state.dataEntity) + '…" value="' + esc(state.dataSearch) + '" style="min-width:200px">' +
62
+ '<select class="input" id="data-limit-select" aria-label="Rows per page">' + limitOptions + '</select>' +
63
+ '<button class="btn" type="button" id="btn-data-refresh">Refresh</button>' +
64
+ '</div>' +
65
+ '<button class="btn primary" type="button" id="btn-insert-record">+ Insert ' + (isColl ? 'Document' : 'Record') + '</button>' +
66
+ '</div>';
67
+
68
+ if (state.dataLoading) {
69
+ return prodBannerHtml + toolbar + '<div class="card" style="padding:48px 24px;text-align:center"><div class="skeleton"></div><div class="skeleton"></div><div class="skeleton"></div></div>';
70
+ }
71
+
72
+ if (state.dataError) {
73
+ return prodBannerHtml + toolbar + '<div class="alert-danger" style="margin-top:12px"><strong>Query error:</strong> ' + esc(state.dataError) + '</div>';
74
+ }
75
+
76
+ var res = state.dataResult;
77
+ if (!res || !res.rows || res.entity !== state.dataEntity) {
78
+ setTimeout(function () { fetchDataRecords(); }, 10);
79
+ return prodBannerHtml + toolbar + '<div class="card" style="padding:48px 24px;text-align:center"><p class="muted">Loading records for ' + esc(state.dataEntity) + '…</p></div>';
80
+ }
81
+
82
+ var pk = res.primaryKey || 'id';
83
+ var schemaCols = currentTable.columns.map(function (c) { return c.name; });
84
+ var allKeys = new Set(schemaCols);
85
+ res.rows.forEach(function (r) { Object.keys(r).forEach(function (k) { allKeys.add(k); }); });
86
+ var displayCols = [pk];
87
+ allKeys.forEach(function (k) { if (k !== pk) displayCols.push(k); });
88
+
89
+ var thead = '<tr><th class="th-actions" style="width:115px">Actions</th>' + displayCols.map(function (col) {
90
+ var isSorted = state.dataSort === col;
91
+ var sortIcon = isSorted ? (state.dataOrder === 'desc' ? ' ▼' : ' ▲') : '';
92
+ return '<th class="sortable" data-sort-col="' + esc(col) + '">' + esc(col) +
93
+ (col === pk ? ' <span class="badge badge-pk">PK</span>' : '') +
94
+ '<span class="th-sort-icon">' + sortIcon + '</span></th>';
95
+ }).join('') + '</tr>';
96
+
97
+ var tbody = '';
98
+ if (res.rows.length === 0) {
99
+ tbody = '<tr><td colspan="' + (displayCols.length + 1) + '" style="text-align:center;padding:32px;color:var(--muted)">No records found' +
100
+ (state.dataSearch ? ' matching "' + esc(state.dataSearch) + '"' : '') + '.</td></tr>';
101
+ } else {
102
+ tbody = res.rows.map(function (row, rowIdx) {
103
+ var actions = '<td class="td-actions">' +
104
+ '<button class="btn small" type="button" data-action="edit-record" data-row="' + rowIdx + '">Edit</button>' +
105
+ '<button class="btn small btn-danger" type="button" data-action="delete-record" data-row="' + rowIdx + '">Delete</button>' +
106
+ '</td>';
107
+
108
+ var cells = displayCols.map(function (c) {
109
+ return renderDataCell(row[c], c);
110
+ }).join('');
111
+
112
+ return '<tr>' + actions + cells + '</tr>';
113
+ }).join('');
114
+ }
115
+
116
+ var start = res.totalCount === 0 ? 0 : (res.page - 1) * res.limit + 1;
117
+ var end = Math.min(res.page * res.limit, res.totalCount);
118
+ var pagination = '<div class="pagination-bar">' +
119
+ '<div>Showing ' + start + '–' + end + ' of ' + res.totalCount + ' ' + (isColl ? 'documents' : 'records') + '</div>' +
120
+ '<div class="pagination-ctrls">' +
121
+ '<button class="btn small" type="button" id="btn-page-prev"' + (res.page <= 1 ? ' disabled' : '') + '>Previous</button>' +
122
+ '<span>Page ' + res.page + ' of ' + Math.max(1, res.totalPages) + '</span>' +
123
+ '<button class="btn small" type="button" id="btn-page-next"' + (res.page >= res.totalPages ? ' disabled' : '') + '>Next</button>' +
124
+ '</div>' +
125
+ '</div>';
126
+
127
+ return prodBannerHtml + toolbar + '<div class="data-grid-wrap"><table class="data-table"><thead>' + thead + '</thead><tbody>' + tbody + '</tbody></table></div>' + pagination;
128
+ }
129
+
130
+ function fetchDataRecords() {
131
+ if (!state.dataEntity) return;
132
+ state.dataLoading = true;
133
+ state.dataError = null;
134
+ renderDb();
135
+
136
+ var params = [
137
+ 'env=' + encodeURIComponent(state.dataEnv),
138
+ 'entity=' + encodeURIComponent(state.dataEntity),
139
+ 'page=' + state.dataPage,
140
+ 'limit=' + state.dataLimit
141
+ ];
142
+ if (state.dataSort) params.push('sort=' + encodeURIComponent(state.dataSort));
143
+ if (state.dataOrder) params.push('order=' + encodeURIComponent(state.dataOrder));
144
+ if (state.dataSearch) params.push('search=' + encodeURIComponent(state.dataSearch));
145
+
146
+ api('/api/data?' + params.join('&')).then(function (res) {
147
+ state.dataResult = res;
148
+ state.dataLoading = false;
149
+ renderDb();
150
+ }, function (err) {
151
+ state.dataError = err.message;
152
+ state.dataLoading = false;
153
+ renderDb();
154
+ });
155
+ }
156
+
157
+ function openInsertRecordModal() {
158
+ var tables = state.schema[state.dataEnv + 'Tables'] || [];
159
+ var table = tables.find(function (t) { return t.name === state.dataEntity; }) || { columns: [] };
160
+ var isColl = isCollection(table);
161
+
162
+ $('record-title').textContent = 'Insert ' + (isColl ? 'Document' : 'Record') + ' into ' + state.dataEntity;
163
+ $('record-lead').textContent = 'Target Environment: ' + state.dataEnv.toUpperCase() + '. Specify column values below:';
164
+ $('btn-save-record').textContent = 'Insert ' + (isColl ? 'Document' : 'Record');
165
+ state.editingPk = null;
166
+
167
+ var html = '';
168
+ table.columns.forEach(function (col) {
169
+ var isPk = col.primaryKey;
170
+ var fieldLabel = esc(col.name) + (isPk ? ' (Primary Key - Optional/Auto)' : '');
171
+ var placeholder = col.type + (col.default ? ' · default: ' + col.default : '');
172
+ var isLong = /json|map|array|text/i.test(col.type);
173
+ if (isLong) {
174
+ html += '<div class="field field-full"><label><b>' + fieldLabel + '</b></label><textarea class="input" name="' + esc(col.name) + '" placeholder="' + esc(placeholder) + '"></textarea></div>';
175
+ } else {
176
+ html += '<div class="field"><label><b>' + fieldLabel + '</b></label><input class="input" type="text" name="' + esc(col.name) + '" placeholder="' + esc(placeholder) + '"></div>';
177
+ }
178
+ });
179
+ $('record-fields').innerHTML = html || '<p class="muted">No schema columns defined.</p>';
180
+ openDialog($('record-dialog'));
181
+ }
182
+
183
+ function openEditRecordModal(rowIndex) {
184
+ if (!state.dataResult || !state.dataResult.rows[rowIndex]) return;
185
+ var row = state.dataResult.rows[rowIndex];
186
+ var pkCol = state.dataResult.primaryKey || 'id';
187
+ var pkVal = row[pkCol];
188
+ state.editingPk = pkVal;
189
+
190
+ var tables = state.schema[state.dataEnv + 'Tables'] || [];
191
+ var table = tables.find(function (t) { return t.name === state.dataEntity; }) || { columns: [] };
192
+ var isColl = isCollection(table);
193
+
194
+ $('record-title').textContent = 'Edit ' + (isColl ? 'Document' : 'Record') + ' (' + pkCol + ' = ' + pkVal + ')';
195
+ $('record-lead').textContent = 'Target Environment: ' + state.dataEnv.toUpperCase() + '. Primary key is locked for single-record isolation:';
196
+ $('btn-save-record').textContent = 'Save Changes';
197
+
198
+ var html = '';
199
+ html += '<div class="field field-full"><label><b>' + esc(pkCol) + ' (Primary Key - Locked)</b></label><input class="input" type="text" name="' + esc(pkCol) + '" value="' + esc(pkVal) + '" disabled style="opacity:.65"></div>';
200
+
201
+ var displayedCols = new Set([pkCol]);
202
+ table.columns.forEach(function (col) {
203
+ if (col.name === pkCol) return;
204
+ displayedCols.add(col.name);
205
+ var val = row[col.name];
206
+ var valStr = val == null ? '' : typeof val === 'object' ? JSON.stringify(val) : String(val);
207
+ var isLong = /json|map|array|text/i.test(col.type) || valStr.length > 50;
208
+ if (isLong) {
209
+ html += '<div class="field field-full"><label><b>' + esc(col.name) + ' (' + esc(col.type) + ')</b></label><textarea class="input" name="' + esc(col.name) + '">' + esc(valStr) + '</textarea></div>';
210
+ } else {
211
+ html += '<div class="field"><label><b>' + esc(col.name) + ' (' + esc(col.type) + ')</b></label><input class="input" type="text" name="' + esc(col.name) + '" value="' + esc(valStr) + '"></div>';
212
+ }
213
+ });
214
+
215
+ Object.keys(row).forEach(function (k) {
216
+ if (displayedCols.has(k)) return;
217
+ var val = row[k];
218
+ var valStr = val == null ? '' : typeof val === 'object' ? JSON.stringify(val) : String(val);
219
+ html += '<div class="field"><label><b>' + esc(k) + '</b></label><input class="input" type="text" name="' + esc(k) + '" value="' + esc(valStr) + '"></div>';
220
+ });
221
+
222
+ $('record-fields').innerHTML = html;
223
+ openDialog($('record-dialog'));
224
+ }
225
+
226
+ function parseInputValue(raw) {
227
+ if (raw === '') return null;
228
+ if (raw === 'true') return true;
229
+ if (raw === 'false') return false;
230
+ if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
231
+ if (/^-?\d+\.\d+$/.test(raw)) return parseFloat(raw);
232
+ if ((raw.startsWith('{') && raw.endsWith('}')) || (raw.startsWith('[') && raw.endsWith(']'))) {
233
+ try { return JSON.parse(raw); } catch (e) { return raw; }
234
+ }
235
+ return raw;
236
+ }
237
+
238
+ function handleSaveRecordSubmit() {
239
+ var form = $('record-form');
240
+ var inputs = form.querySelectorAll('input, textarea');
241
+ var data = {};
242
+ inputs.forEach(function (inp) {
243
+ if (inp.name && !inp.disabled) {
244
+ var v = inp.value.trim();
245
+ if (v !== '') data[inp.name] = parseInputValue(v);
246
+ }
247
+ });
248
+
249
+ if (state.editingPk !== null) {
250
+ dispatchMutation('UPDATE', state.dataEntity, state.editingPk, { updates: data });
251
+ } else {
252
+ dispatchMutation('INSERT', state.dataEntity, null, { record: data });
253
+ }
254
+ }
255
+
256
+ function handleDeleteRecord(rowIndex) {
257
+ if (!state.dataResult || !state.dataResult.rows[rowIndex]) return;
258
+ var row = state.dataResult.rows[rowIndex];
259
+ var pkCol = state.dataResult.primaryKey || 'id';
260
+ var pkVal = row[pkCol];
261
+ if (pkVal === undefined || pkVal === null) return;
262
+
263
+ if (state.dataEnv === 'prod') {
264
+ dispatchMutation('DELETE', state.dataEntity, pkVal, {});
265
+ } else {
266
+ if (window.confirm('Delete record ' + pkVal + ' from ' + state.dataEntity + '?')) {
267
+ api('/api/data', {
268
+ method: 'DELETE',
269
+ headers: { 'Content-Type': 'application/json' },
270
+ body: JSON.stringify({ env: state.dataEnv, entity: state.dataEntity, primaryKey: pkVal })
271
+ }).then(function () {
272
+ toast('Record deleted successfully', 'ok');
273
+ fetchDataRecords();
274
+ }, function (err) {
275
+ toast('Delete failed: ' + err.message);
276
+ });
277
+ }
278
+ }
279
+ }
280
+
281
+ function dispatchMutation(action, entity, pk, payload) {
282
+ if (state.dataEnv === 'prod') {
283
+ state.pendingMutation = { action: action, entity: entity, pk: pk, payload: payload };
284
+ $('challenge-desc').textContent = 'Action: ' + action + ' on entity "' + entity + '"' + (pk != null ? ' (PK: ' + pk + ')' : '') + '.';
285
+ $('challenge-target-name').textContent = entity;
286
+ var input = $('challenge-input');
287
+ input.value = '';
288
+ $('btn-confirm-challenge').disabled = true;
289
+ openDialog($('challenge-dialog'));
290
+ input.focus();
291
+ } else {
292
+ executeMutationDirect(action, entity, pk, payload, false, '');
293
+ }
294
+ }
295
+
296
+ function executeMutationDirect(action, entity, pk, payload, confirmProd, challengePhrase) {
297
+ var method = action === 'INSERT' ? 'POST' : action === 'UPDATE' ? 'PUT' : 'DELETE';
298
+ var body = Object.assign({
299
+ env: state.dataEnv,
300
+ entity: entity
301
+ }, payload);
302
+ if (pk != null) body.primaryKey = pk;
303
+ if (confirmProd) {
304
+ body.confirmProd = true;
305
+ body.challengePhrase = challengePhrase;
306
+ }
307
+
308
+ api('/api/data', {
309
+ method: method,
310
+ headers: { 'Content-Type': 'application/json' },
311
+ body: JSON.stringify(body)
312
+ }).then(function () {
313
+ if (confirmProd) {
314
+ toast('Production ' + action.toLowerCase() + ' executed. Logged to .nativ/prod_audit.log', 'ok');
315
+ $('challenge-dialog').close();
316
+ } else {
317
+ toast(action + ' executed successfully', 'ok');
318
+ }
319
+ $('record-dialog').close();
320
+ fetchDataRecords();
321
+ }, function (err) {
322
+ toast(action + ' failed: ' + err.message);
323
+ });
324
+ }
325
+
326
+ `;
@@ -0,0 +1 @@
1
+ export declare const databaseConnectionScript: string;