mustflow 1.31.0 → 2.16.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 (66) hide show
  1. package/README.md +23 -9
  2. package/dist/cli/commands/classify.js +61 -6
  3. package/dist/cli/commands/contract-lint.js +13 -4
  4. package/dist/cli/commands/dashboard.js +77 -2
  5. package/dist/cli/commands/explain-verify.js +11 -1
  6. package/dist/cli/commands/index.js +14 -0
  7. package/dist/cli/commands/run.js +4 -1
  8. package/dist/cli/commands/verify.js +986 -43
  9. package/dist/cli/i18n/en.js +61 -10
  10. package/dist/cli/i18n/es.js +61 -10
  11. package/dist/cli/i18n/fr.js +61 -10
  12. package/dist/cli/i18n/hi.js +61 -10
  13. package/dist/cli/i18n/ko.js +61 -10
  14. package/dist/cli/i18n/zh.js +61 -10
  15. package/dist/cli/lib/dashboard-export.js +62 -12
  16. package/dist/cli/lib/dashboard-html/client-script.js +1936 -0
  17. package/dist/cli/lib/dashboard-html/locale-bootstrap.js +8 -0
  18. package/dist/cli/lib/dashboard-html/styles.js +572 -0
  19. package/dist/cli/lib/dashboard-html/template.js +134 -0
  20. package/dist/cli/lib/dashboard-html/types.js +1 -0
  21. package/dist/cli/lib/dashboard-html.js +1 -1907
  22. package/dist/cli/lib/dashboard-locale.js +37 -0
  23. package/dist/cli/lib/local-index/constants.js +48 -0
  24. package/dist/cli/lib/local-index/index.js +2951 -0
  25. package/dist/cli/lib/local-index/sql.js +15 -0
  26. package/dist/cli/lib/local-index/types.js +1 -0
  27. package/dist/cli/lib/local-index.js +1 -1911
  28. package/dist/cli/lib/run-plan.js +76 -1
  29. package/dist/cli/lib/templates.js +18 -1
  30. package/dist/cli/lib/validation/command-intents.js +11 -0
  31. package/dist/cli/lib/validation/constants.js +238 -0
  32. package/dist/cli/lib/validation/index.js +1384 -0
  33. package/dist/cli/lib/validation/primitives.js +198 -0
  34. package/dist/cli/lib/validation/test-selection.js +95 -0
  35. package/dist/cli/lib/validation/types.js +1 -0
  36. package/dist/cli/lib/validation.js +1 -1770
  37. package/dist/core/check-issues.js +6 -0
  38. package/dist/core/completion-verdict.js +341 -0
  39. package/dist/core/contract-lint.js +221 -6
  40. package/dist/core/external-evidence.js +9 -0
  41. package/dist/core/public-json-contracts.js +21 -0
  42. package/dist/core/repeated-failure.js +179 -0
  43. package/dist/core/repro-evidence.js +134 -0
  44. package/dist/core/scope-risk.js +64 -0
  45. package/dist/core/skill-route-alignment.js +20 -0
  46. package/dist/core/source-anchor-status.js +4 -1
  47. package/dist/core/test-selection.js +3 -0
  48. package/dist/core/validation-ratchet.js +196 -0
  49. package/dist/core/verification-evidence.js +249 -0
  50. package/examples/README.md +12 -4
  51. package/package.json +3 -3
  52. package/schemas/README.md +13 -3
  53. package/schemas/change-verification-report.schema.json +16 -2
  54. package/schemas/commands.schema.json +4 -0
  55. package/schemas/contract-lint-report.schema.json +29 -0
  56. package/schemas/dashboard-export.schema.json +310 -0
  57. package/schemas/explain-report.schema.json +173 -1
  58. package/schemas/latest-run-pointer.schema.json +601 -0
  59. package/schemas/run-receipt.schema.json +4 -0
  60. package/schemas/test-selection.schema.json +81 -0
  61. package/schemas/verify-report.schema.json +578 -1
  62. package/schemas/verify-run-manifest.schema.json +627 -0
  63. package/templates/default/i18n.toml +1 -1
  64. package/templates/default/locales/en/.mustflow/skills/INDEX.md +124 -29
  65. package/templates/default/locales/en/.mustflow/skills/routes.toml +289 -0
  66. package/templates/default/manifest.toml +29 -2
@@ -1,1907 +1 @@
1
- import { getDashboardLocaleBundle } from './dashboard-locale.js';
2
- function escapeHtml(value) {
3
- return value
4
- .replace(/&/g, '&')
5
- .replace(/</g, '&lt;')
6
- .replace(/>/g, '&gt;')
7
- .replace(/"/g, '&quot;')
8
- .replace(/'/g, '&#39;');
9
- }
10
- export function renderDashboardHtml(snapshot, token, statusSnapshot, docReviewSnapshot) {
11
- const root = escapeHtml(snapshot.projectRoot);
12
- const preferencesPath = escapeHtml(snapshot.preferencesPath);
13
- const serializedSnapshot = JSON.stringify(snapshot);
14
- const serializedStatusSnapshot = JSON.stringify(statusSnapshot);
15
- const serializedDocReviewSnapshot = JSON.stringify(docReviewSnapshot);
16
- const serializedToken = JSON.stringify(token);
17
- const localeBundle = getDashboardLocaleBundle();
18
- const serializedLocaleBundle = JSON.stringify(localeBundle);
19
- const serializedAvailableLocales = JSON.stringify(localeBundle.locales);
20
- return `<!doctype html>
21
- <html lang="en">
22
- <head>
23
- <meta charset="utf-8">
24
- <meta name="viewport" content="width=device-width, initial-scale=1">
25
- <title>mustflow dashboard</title>
26
- <style>
27
- :root {
28
- color-scheme: light dark;
29
- --bg: #101216;
30
- --panel: #181b21;
31
- --line: #2a2f3a;
32
- --text: #eef1f7;
33
- --muted: #aeb6c5;
34
- --accent: #8fb4ff;
35
- --danger: #ff9a9a;
36
- --ok: #9be7ba;
37
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
38
- }
39
- * { box-sizing: border-box; }
40
- body {
41
- margin: 0;
42
- background: var(--bg);
43
- color: var(--text);
44
- font-size: 17px;
45
- }
46
- header {
47
- border-bottom: 1px solid var(--line);
48
- padding: 16px 20px;
49
- }
50
- .title-row {
51
- align-items: center;
52
- display: flex;
53
- gap: 8px;
54
- margin-bottom: 8px;
55
- }
56
- h1 {
57
- font-size: 22px;
58
- line-height: 1.2;
59
- margin: 0;
60
- font-weight: 650;
61
- }
62
- .icon-button {
63
- align-items: center;
64
- display: inline-flex;
65
- height: 34px;
66
- justify-content: center;
67
- padding: 0;
68
- width: 34px;
69
- }
70
- .icon-button svg {
71
- height: 18px;
72
- width: 18px;
73
- }
74
- .path {
75
- color: var(--muted);
76
- font-size: 14px;
77
- overflow-wrap: anywhere;
78
- }
79
- main {
80
- max-width: 980px;
81
- margin: 0 auto;
82
- padding: 20px;
83
- }
84
- .toolbar {
85
- align-items: center;
86
- display: flex;
87
- flex-wrap: wrap;
88
- gap: 12px;
89
- justify-content: flex-end;
90
- margin-bottom: 14px;
91
- min-height: 36px;
92
- }
93
- .tabs {
94
- display: flex;
95
- gap: 8px;
96
- margin-bottom: 14px;
97
- overflow-x: auto;
98
- }
99
- .tab {
100
- border-color: transparent;
101
- min-width: 110px;
102
- }
103
- .tab[aria-selected="true"] {
104
- background: var(--panel);
105
- border-color: var(--line);
106
- }
107
- .tab-panel[hidden] {
108
- display: none;
109
- }
110
- .language-picker {
111
- align-items: center;
112
- display: inline-flex;
113
- flex-shrink: 0;
114
- gap: 8px;
115
- }
116
- .language-picker span {
117
- color: var(--muted);
118
- font-size: 14px;
119
- white-space: nowrap;
120
- }
121
- .language-picker select {
122
- min-width: 130px;
123
- }
124
- .status {
125
- color: var(--muted);
126
- font-size: 15px;
127
- margin-right: auto;
128
- }
129
- .status.ok { color: var(--ok); }
130
- .status.error { color: var(--danger); }
131
- button, select, input {
132
- background: #11141a;
133
- border: 1px solid var(--line);
134
- border-radius: 6px;
135
- color: var(--text);
136
- font: inherit;
137
- min-height: 38px;
138
- }
139
- button {
140
- cursor: pointer;
141
- padding: 0 14px;
142
- }
143
- button:disabled {
144
- cursor: not-allowed;
145
- opacity: 0.6;
146
- }
147
- section {
148
- border-top: 1px solid var(--line);
149
- padding: 14px 0;
150
- }
151
- h2 {
152
- font-size: 15px;
153
- line-height: 1.25;
154
- margin: 0 0 8px;
155
- color: var(--muted);
156
- font-weight: 650;
157
- }
158
- .setting {
159
- align-items: center;
160
- display: grid;
161
- gap: 12px;
162
- grid-template-columns: minmax(220px, 1fr) minmax(120px, 240px);
163
- min-height: 48px;
164
- padding: 6px 0;
165
- }
166
- .label {
167
- font-size: 16px;
168
- }
169
- .value-description {
170
- color: var(--muted);
171
- font-size: 13px;
172
- line-height: 1.35;
173
- margin-left: 10px;
174
- }
175
- .meta {
176
- color: var(--muted);
177
- font-size: 13px;
178
- margin-top: 2px;
179
- }
180
- .status-grid {
181
- display: grid;
182
- gap: 8px;
183
- grid-template-columns: repeat(2, minmax(0, 1fr));
184
- }
185
- .status-item {
186
- border-bottom: 1px solid var(--line);
187
- display: grid;
188
- gap: 4px;
189
- padding: 8px 0;
190
- }
191
- .status-label {
192
- color: var(--muted);
193
- font-size: 13px;
194
- }
195
- .status-value {
196
- font-size: 15px;
197
- overflow-wrap: anywhere;
198
- }
199
- .status-value.ok { color: var(--ok); }
200
- .status-value.warn { color: var(--danger); }
201
- .issue-list {
202
- margin: 0;
203
- padding-left: 18px;
204
- }
205
- .issue-list li {
206
- margin: 4px 0;
207
- overflow-wrap: anywhere;
208
- }
209
- .command-row {
210
- border-bottom: 1px solid var(--line);
211
- display: grid;
212
- gap: 10px;
213
- grid-template-columns: minmax(160px, 220px) 1fr;
214
- padding: 10px 0;
215
- }
216
- .command-name {
217
- font-weight: 650;
218
- overflow-wrap: anywhere;
219
- }
220
- .command-state {
221
- color: var(--muted);
222
- font-size: 13px;
223
- margin-top: 4px;
224
- }
225
- .command-state.ok { color: var(--ok); }
226
- .command-state.warn { color: var(--danger); }
227
- .command-description {
228
- font-size: 15px;
229
- overflow-wrap: anywhere;
230
- }
231
- .command-meta {
232
- color: var(--muted);
233
- display: flex;
234
- flex-wrap: wrap;
235
- font-size: 13px;
236
- gap: 8px 14px;
237
- margin-top: 6px;
238
- }
239
- .command-note {
240
- color: var(--muted);
241
- font-size: 13px;
242
- margin-top: 6px;
243
- overflow-wrap: anywhere;
244
- }
245
- .verification-row {
246
- border-bottom: 1px solid var(--line);
247
- display: grid;
248
- gap: 10px;
249
- grid-template-columns: minmax(160px, 220px) 1fr auto;
250
- padding: 10px 0;
251
- }
252
- .verification-command {
253
- font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
254
- font-size: 14px;
255
- overflow-wrap: anywhere;
256
- }
257
- .verification-files {
258
- color: var(--muted);
259
- font-size: 13px;
260
- margin-top: 6px;
261
- overflow-wrap: anywhere;
262
- }
263
- .verification-copy {
264
- min-height: 34px;
265
- padding: 0 10px;
266
- }
267
- .doc-controls {
268
- display: grid;
269
- gap: 10px;
270
- margin-bottom: 14px;
271
- }
272
- .doc-filter-controls,
273
- .doc-review-controls {
274
- align-items: end;
275
- display: grid;
276
- gap: 10px;
277
- }
278
- .doc-filter-controls {
279
- grid-template-columns: minmax(130px, 160px) minmax(180px, 1fr);
280
- }
281
- .doc-review-controls {
282
- grid-template-columns: minmax(130px, 170px) minmax(160px, 1fr) minmax(180px, 1fr);
283
- }
284
- .doc-controls label,
285
- .doc-control-group-label {
286
- display: grid;
287
- gap: 5px;
288
- }
289
- .doc-controls span,
290
- .doc-control-group-label {
291
- color: var(--muted);
292
- font-size: 13px;
293
- }
294
- .doc-list {
295
- border-top: 1px solid var(--line);
296
- }
297
- .doc-row {
298
- align-items: center;
299
- border-bottom: 1px solid var(--line);
300
- display: grid;
301
- gap: 12px;
302
- grid-template-columns: minmax(0, 1fr) minmax(90px, auto) auto;
303
- padding: 10px 0;
304
- }
305
- .doc-path {
306
- font-size: 15px;
307
- overflow-wrap: anywhere;
308
- }
309
- .doc-meta {
310
- color: var(--muted);
311
- font-size: 13px;
312
- margin-top: 3px;
313
- overflow-wrap: anywhere;
314
- }
315
- .doc-comment {
316
- background: rgba(255, 255, 255, 0.04);
317
- border: 1px solid var(--line);
318
- border-radius: 6px;
319
- color: var(--text);
320
- font: inherit;
321
- font-size: 13px;
322
- line-height: 1.45;
323
- margin: 8px 0 0;
324
- max-height: 180px;
325
- overflow: auto;
326
- padding: 8px;
327
- white-space: pre-wrap;
328
- }
329
- .doc-status {
330
- color: var(--muted);
331
- font-size: 13px;
332
- white-space: nowrap;
333
- }
334
- .doc-actions {
335
- display: flex;
336
- flex-wrap: wrap;
337
- gap: 8px;
338
- justify-content: flex-end;
339
- }
340
- .doc-actions button {
341
- min-height: 34px;
342
- padding: 0 10px;
343
- }
344
- .empty {
345
- color: var(--muted);
346
- padding: 14px 0;
347
- }
348
- select, input[type="number"], input[type="text"] {
349
- padding: 0 12px;
350
- width: 100%;
351
- }
352
- select {
353
- appearance: none;
354
- background-image:
355
- linear-gradient(45deg, transparent 50%, currentColor 50%),
356
- linear-gradient(135deg, currentColor 50%, transparent 50%);
357
- background-position:
358
- calc(100% - 22px) 50%,
359
- calc(100% - 16px) 50%;
360
- background-repeat: no-repeat;
361
- background-size: 6px 6px, 6px 6px;
362
- padding-right: 44px;
363
- }
364
- select::-ms-expand {
365
- display: none;
366
- }
367
- .setting-control {
368
- width: 100%;
369
- }
370
- .locale-tag-control {
371
- display: grid;
372
- gap: 8px;
373
- width: 100%;
374
- }
375
- .locale-tag-control input[hidden] {
376
- display: none;
377
- }
378
- input[type="checkbox"] {
379
- height: 22px;
380
- justify-self: end;
381
- min-height: 22px;
382
- width: 22px;
383
- }
384
- @media (max-width: 640px) {
385
- main { padding: 14px; }
386
- .setting {
387
- grid-template-columns: 1fr;
388
- gap: 6px;
389
- }
390
- input[type="checkbox"] {
391
- justify-self: start;
392
- }
393
- .doc-controls,
394
- .doc-filter-controls,
395
- .doc-review-controls,
396
- .doc-row,
397
- .verification-row,
398
- .status-grid {
399
- grid-template-columns: 1fr;
400
- }
401
- .doc-actions {
402
- justify-content: flex-start;
403
- }
404
- }
405
- </style>
406
- </head>
407
- <body>
408
- <header>
409
- <div class="title-row">
410
- <h1 id="dashboard-title">mustflow dashboard</h1>
411
- <button id="open-mustflow" class="icon-button" type="button" aria-label="Open .mustflow folder" title="Open .mustflow folder">
412
- <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
413
- <path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.2a2 2 0 0 1-1.6-.8L10 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2Z"></path>
414
- </svg>
415
- </button>
416
- </div>
417
- <div class="path">${root}</div>
418
- <div class="path">${preferencesPath}</div>
419
- </header>
420
- <main>
421
- <nav class="tabs" aria-label="Dashboard sections">
422
- <button id="tab-status" class="tab" type="button" data-tab="status" aria-controls="panel-status" aria-selected="true">Status</button>
423
- <button id="tab-verification" class="tab" type="button" data-tab="verification" aria-controls="panel-verification" aria-selected="false">Verification</button>
424
- <button id="tab-commands" class="tab" type="button" data-tab="commands" aria-controls="panel-commands" aria-selected="false">Commands</button>
425
- <button id="tab-release" class="tab" type="button" data-tab="release" aria-controls="panel-release" aria-selected="false">Release</button>
426
- <button id="tab-update" class="tab" type="button" data-tab="update" aria-controls="panel-update" aria-selected="false">Update</button>
427
- <button id="tab-runs" class="tab" type="button" data-tab="runs" aria-controls="panel-runs" aria-selected="false">Runs</button>
428
- <button id="tab-skills" class="tab" type="button" data-tab="skills" aria-controls="panel-skills" aria-selected="false">Skills</button>
429
- <button id="tab-settings" class="tab" type="button" data-tab="settings" aria-controls="panel-settings" aria-selected="false">Settings</button>
430
- <button id="tab-documents" class="tab" type="button" data-tab="documents" aria-controls="panel-documents" aria-selected="false">Documents</button>
431
- </nav>
432
- <div class="toolbar">
433
- <div id="status" class="status">No changes</div>
434
- <label class="language-picker" for="dashboard-language">
435
- <span id="dashboard-language-label">Language</span>
436
- <select id="dashboard-language" name="dashboard-language"></select>
437
- </label>
438
- <button id="reload" type="button">Reload</button>
439
- <button id="save" type="button" disabled>Save</button>
440
- </div>
441
- <div id="panel-status" class="tab-panel">
442
- <div id="dashboard-status"></div>
443
- </div>
444
- <div id="panel-verification" class="tab-panel" hidden>
445
- <div id="dashboard-verification"></div>
446
- </div>
447
- <div id="panel-commands" class="tab-panel" hidden>
448
- <div id="dashboard-commands"></div>
449
- </div>
450
- <div id="panel-release" class="tab-panel" hidden>
451
- <div id="dashboard-release"></div>
452
- </div>
453
- <div id="panel-update" class="tab-panel" hidden>
454
- <div id="dashboard-update"></div>
455
- </div>
456
- <div id="panel-runs" class="tab-panel" hidden>
457
- <div id="dashboard-runs"></div>
458
- </div>
459
- <div id="panel-skills" class="tab-panel" hidden>
460
- <div id="dashboard-skills"></div>
461
- </div>
462
- <div id="panel-settings" class="tab-panel" hidden>
463
- <div id="settings"></div>
464
- </div>
465
- <div id="panel-documents" class="tab-panel" hidden>
466
- <div class="doc-controls">
467
- <div class="doc-filter-controls">
468
- <label for="doc-status-filter">
469
- <span id="doc-status-filter-label">Status</span>
470
- <select id="doc-status-filter"></select>
471
- </label>
472
- <label for="doc-path-filter">
473
- <span id="doc-path-filter-label">File name</span>
474
- <input id="doc-path-filter" type="text" autocomplete="off" spellcheck="false">
475
- </label>
476
- </div>
477
- <div class="doc-control-group-label" id="doc-review-fields-label">Review record</div>
478
- <div class="doc-review-controls">
479
- <label for="doc-reviewer-kind">
480
- <span id="doc-reviewer-kind-label">Reviewer kind</span>
481
- <select id="doc-reviewer-kind"></select>
482
- </label>
483
- <label for="doc-reviewer-id">
484
- <span id="doc-reviewer-id-label">Reviewer ID</span>
485
- <input id="doc-reviewer-id" type="text" autocomplete="off" spellcheck="false">
486
- </label>
487
- <label for="doc-review-summary">
488
- <span id="doc-review-summary-label">Summary</span>
489
- <input id="doc-review-summary" type="text" autocomplete="off">
490
- </label>
491
- </div>
492
- </div>
493
- <div id="docs-review-list" class="doc-list"></div>
494
- </div>
495
- </main>
496
- <script>
497
- const initialSnapshot = ${serializedSnapshot};
498
- const dashboardToken = ${serializedToken};
499
- const dashboardLocales = ${serializedLocaleBundle};
500
- const availableLocales = ${serializedAvailableLocales};
501
- const initialStatusSnapshot = ${serializedStatusSnapshot};
502
- const initialDocReview = ${serializedDocReviewSnapshot};
503
- let snapshot = initialSnapshot;
504
- let pending = new Map();
505
- let currentLocale = resolveInitialLocale();
506
- let statusState = { key: "dashboard.ui.noChanges", text: "", type: "" };
507
- let currentTab = "status";
508
- let dashboardStatus = initialStatusSnapshot;
509
- let docReview = initialDocReview;
510
-
511
- const groups = [
512
- ["dashboard.group.git", ["git.auto_stage", "git.auto_commit", "git.auto_push"]],
513
- ["dashboard.group.commitMessage", "git.commit_message."],
514
- ["dashboard.group.reporting", "reporting."],
515
- ["dashboard.group.verification", "verification.selection."],
516
- ["dashboard.group.testAuthoring", "testing.authoring."],
517
- ["dashboard.group.codeStyle", "code_style."],
518
- ["dashboard.group.refactoring", "refactoring.hotspots."],
519
- ["dashboard.group.versioning", "release.versioning."]
520
- ];
521
- const docStatusFilters = ["active", "pending", "in_review", "changes_made", "needs_human", "approved", "ignored", "all"];
522
- const reviewerKinds = ["human", "llm", "tool", "external"];
523
-
524
- function resolveInitialLocale() {
525
- const stored = window.localStorage.getItem("mustflow.dashboard.language");
526
- if (availableLocales.includes(stored)) return stored;
527
- const browserLocale = (window.navigator.language || "").slice(0, 2).toLowerCase();
528
- return availableLocales.includes(browserLocale) ? browserLocale : "en";
529
- }
530
-
531
- function message(key) {
532
- return dashboardLocales.messages[currentLocale]?.[key] ?? dashboardLocales.messages.en[key] ?? key;
533
- }
534
-
535
- function messageExists(key) {
536
- return Boolean(dashboardLocales.messages[currentLocale]?.[key] ?? dashboardLocales.messages.en[key]);
537
- }
538
-
539
- function statusText(text, type = "") {
540
- statusState = { key: "", text, type };
541
- renderStatus();
542
- }
543
-
544
- function statusKey(key, type = "") {
545
- statusState = { key, text: "", type };
546
- renderStatus();
547
- }
548
-
549
- function renderStatus() {
550
- const element = document.getElementById("status");
551
- const text = statusState.key ? message(statusState.key) : statusState.text;
552
- element.textContent = text;
553
- element.className = statusState.type ? "status " + statusState.type : "status";
554
- }
555
-
556
- function settingValue(id) {
557
- return pending.has(id) ? pending.get(id) : snapshot.settings.find((setting) => setting.id === id)?.value;
558
- }
559
-
560
- function settingDescriptionKey(setting) {
561
- const valueSpecificKey = "dashboard.setting." + setting.id + ".description." + String(settingValue(setting.id));
562
- if (messageExists(valueSpecificKey)) return valueSpecificKey;
563
- const key = "dashboard.setting." + setting.id + ".description";
564
- return messageExists(key) ? key : "";
565
- }
566
-
567
- function settingDescription(setting) {
568
- const key = settingDescriptionKey(setting);
569
- return key ? message(key) : "";
570
- }
571
-
572
- function updateSettingDescription(id) {
573
- const setting = snapshot.settings.find((item) => item.id === id);
574
- if (!setting) return;
575
- const element = document.getElementById(controlId(setting) + "-description");
576
- if (element) element.textContent = settingDescription(setting);
577
- }
578
-
579
- function setPending(id, value) {
580
- const original = snapshot.settings.find((setting) => setting.id === id)?.value;
581
- if (Object.is(original, value)) {
582
- pending.delete(id);
583
- } else {
584
- pending.set(id, value);
585
- }
586
- document.getElementById("save").disabled = pending.size === 0;
587
- statusKey(pending.size === 0 ? "dashboard.ui.noChanges" : "dashboard.ui.unsavedChanges");
588
- updateSettingDescription(id);
589
- }
590
-
591
- function controlId(setting) {
592
- return "setting-" + setting.id.replace(/[^a-zA-Z0-9_-]/g, "-");
593
- }
594
-
595
- function renderInput(setting) {
596
- if (setting.kind === "boolean") {
597
- const input = document.createElement("input");
598
- const inputId = controlId(setting);
599
- input.id = inputId;
600
- input.name = setting.id;
601
- input.type = "checkbox";
602
- input.checked = Boolean(settingValue(setting.id));
603
- input.disabled = !setting.editable;
604
- input.addEventListener("change", () => setPending(setting.id, input.checked));
605
- return input;
606
- }
607
-
608
- if (setting.kind === "number") {
609
- const input = document.createElement("input");
610
- const inputId = controlId(setting);
611
- input.id = inputId;
612
- input.name = setting.id;
613
- input.type = "number";
614
- input.value = String(settingValue(setting.id));
615
- if (setting.min !== undefined) input.min = String(setting.min);
616
- if (setting.max !== undefined) input.max = String(setting.max);
617
- input.disabled = !setting.editable;
618
- input.addEventListener("input", () => setPending(setting.id, Number(input.value)));
619
- return input;
620
- }
621
-
622
- if (setting.acceptsLocaleTag) {
623
- const wrapper = document.createElement("div");
624
- const select = document.createElement("select");
625
- const customInput = document.createElement("input");
626
- const inputId = controlId(setting);
627
- const customInputId = inputId + "-custom";
628
- const optionValues = setting.options || [];
629
- const currentValue = String(settingValue(setting.id));
630
- const customLocaleOptionValue = "__mustflow_custom_locale__";
631
- const isCustomValue = !optionValues.includes(currentValue);
632
- wrapper.className = "locale-tag-control";
633
- select.id = inputId;
634
- select.name = setting.id;
635
- for (const option of optionValues) {
636
- const child = document.createElement("option");
637
- child.value = option;
638
- child.textContent = option;
639
- child.selected = option === currentValue;
640
- select.appendChild(child);
641
- }
642
- const customChild = document.createElement("option");
643
- customChild.value = customLocaleOptionValue;
644
- customChild.textContent = message("dashboard.ui.customLocale");
645
- customChild.selected = isCustomValue;
646
- select.appendChild(customChild);
647
- select.disabled = !setting.editable;
648
-
649
- customInput.id = customInputId;
650
- customInput.name = setting.id + ".custom";
651
- customInput.type = "text";
652
- customInput.autocomplete = "off";
653
- customInput.spellcheck = false;
654
- customInput.placeholder = "pt-BR";
655
- customInput.value = isCustomValue ? currentValue : "";
656
- customInput.hidden = !isCustomValue;
657
- customInput.disabled = !setting.editable || !isCustomValue;
658
- customInput.setAttribute("aria-label", message("dashboard.ui.customLocale"));
659
-
660
- function updateCustomPending() {
661
- const value = customInput.value.trim();
662
- if (value.length > 0) {
663
- setPending(setting.id, value);
664
- } else {
665
- const original = snapshot.settings.find((item) => item.id === setting.id)?.value;
666
- if (original !== undefined) {
667
- setPending(setting.id, original);
668
- }
669
- }
670
- }
671
-
672
- select.addEventListener("change", () => {
673
- const customSelected = select.value === customLocaleOptionValue;
674
- customInput.hidden = !customSelected;
675
- customInput.disabled = !setting.editable || !customSelected;
676
- if (customSelected) {
677
- customInput.focus();
678
- updateCustomPending();
679
- } else {
680
- setPending(setting.id, select.value);
681
- }
682
- });
683
- customInput.addEventListener("input", updateCustomPending);
684
- wrapper.appendChild(select);
685
- wrapper.appendChild(customInput);
686
- return wrapper;
687
- }
688
-
689
- const select = document.createElement("select");
690
- const inputId = controlId(setting);
691
- select.id = inputId;
692
- select.name = setting.id;
693
- for (const option of setting.options || []) {
694
- const child = document.createElement("option");
695
- child.value = option;
696
- child.textContent = option;
697
- child.selected = option === settingValue(setting.id);
698
- select.appendChild(child);
699
- }
700
- select.disabled = !setting.editable;
701
- select.addEventListener("change", () => setPending(setting.id, select.value));
702
- return select;
703
- }
704
-
705
- function render() {
706
- const root = document.getElementById("settings");
707
- root.textContent = "";
708
- for (const [titleKey, matcher] of groups) {
709
- const settings = Array.isArray(matcher)
710
- ? snapshot.settings.filter((setting) => matcher.includes(setting.id))
711
- : snapshot.settings.filter((setting) => setting.id.startsWith(matcher));
712
- if (settings.length === 0) continue;
713
- const section = document.createElement("section");
714
- const heading = document.createElement("h2");
715
- heading.textContent = message(titleKey);
716
- section.appendChild(heading);
717
- for (const setting of settings) {
718
- const row = document.createElement("div");
719
- row.className = "setting";
720
- const label = document.createElement("label");
721
- label.htmlFor = controlId(setting);
722
- const labelText = document.createElement("div");
723
- labelText.className = "label";
724
- const labelName = document.createElement("span");
725
- labelName.textContent = message("dashboard.setting." + setting.id) || setting.label;
726
- labelText.appendChild(labelName);
727
- const descriptionText = settingDescription(setting);
728
- if (descriptionText) {
729
- const description = document.createElement("span");
730
- description.id = controlId(setting) + "-description";
731
- description.className = "value-description";
732
- description.textContent = descriptionText;
733
- labelText.appendChild(description);
734
- }
735
- label.appendChild(labelText);
736
- if (!setting.editable) {
737
- const meta = document.createElement("div");
738
- meta.className = "meta";
739
- meta.textContent = setting.lockedReason
740
- ? message("dashboard.ui.locked") + ": " + message(setting.lockedReason)
741
- : message("dashboard.ui.locked");
742
- label.appendChild(meta);
743
- }
744
- row.appendChild(label);
745
- row.appendChild(renderInput(setting));
746
- section.appendChild(row);
747
- }
748
- root.appendChild(section);
749
- }
750
- }
751
-
752
- function renderLocaleSelector() {
753
- const select = document.getElementById("dashboard-language");
754
- select.textContent = "";
755
- for (const locale of availableLocales) {
756
- const option = document.createElement("option");
757
- option.value = locale;
758
- option.textContent = dashboardLocales.names[locale] ?? locale;
759
- option.selected = locale === currentLocale;
760
- select.appendChild(option);
761
- }
762
- }
763
-
764
- function renderChrome() {
765
- document.documentElement.lang = currentLocale;
766
- document.getElementById("dashboard-title").textContent = message("dashboard.ui.title");
767
- document.getElementById("tab-status").textContent = message("dashboard.tab.status");
768
- document.getElementById("tab-verification").textContent = message("dashboard.tab.verification") + " (" + dashboardStatus.verification.recommendations.length + ")";
769
- document.getElementById("tab-commands").textContent = message("dashboard.tab.commands");
770
- document.getElementById("tab-release").textContent = message("dashboard.tab.release");
771
- document.getElementById("tab-update").textContent = message("dashboard.tab.update") + " (" + (dashboardStatus.update.blockers.length + dashboardStatus.update.changes.length) + ")";
772
- document.getElementById("tab-runs").textContent = message("dashboard.tab.runs");
773
- document.getElementById("tab-skills").textContent = message("dashboard.tab.skills") + " (" + dashboardStatus.skills.count + ")";
774
- document.getElementById("tab-settings").textContent = message("dashboard.tab.settings");
775
- document.getElementById("tab-documents").textContent = message("dashboard.tab.documents") + " (" + docReview.count + ")";
776
- const openMustflow = document.getElementById("open-mustflow");
777
- openMustflow.title = message("dashboard.ui.openMustflow");
778
- openMustflow.setAttribute("aria-label", message("dashboard.ui.openMustflow"));
779
- document.getElementById("dashboard-language-label").textContent = message("dashboard.ui.language");
780
- document.getElementById("reload").textContent = message("dashboard.ui.reload");
781
- document.getElementById("save").textContent = message("dashboard.ui.save");
782
- document.getElementById("save").hidden = currentTab !== "settings";
783
- document.getElementById("doc-status-filter-label").textContent = message("dashboard.docs.statusFilter");
784
- document.getElementById("doc-path-filter-label").textContent = message("dashboard.docs.pathFilter");
785
- document.getElementById("doc-review-fields-label").textContent = message("dashboard.docs.reviewFields");
786
- document.getElementById("doc-reviewer-kind-label").textContent = message("dashboard.docs.reviewerKind");
787
- document.getElementById("doc-reviewer-id-label").textContent = message("dashboard.docs.reviewerId");
788
- document.getElementById("doc-review-summary-label").textContent = message("dashboard.docs.summary");
789
- document.getElementById("doc-path-filter").placeholder = message("dashboard.docs.pathFilterPlaceholder");
790
- document.getElementById("doc-reviewer-id").placeholder = message("dashboard.docs.reviewerIdPlaceholder");
791
- document.getElementById("doc-review-summary").placeholder = message("dashboard.docs.summaryPlaceholder");
792
- renderStatus();
793
- }
794
-
795
- function renderTabState() {
796
- for (const tab of document.querySelectorAll(".tab")) {
797
- const selected = tab.dataset.tab === currentTab;
798
- tab.setAttribute("aria-selected", selected ? "true" : "false");
799
- }
800
- document.getElementById("panel-status").hidden = currentTab !== "status";
801
- document.getElementById("panel-verification").hidden = currentTab !== "verification";
802
- document.getElementById("panel-commands").hidden = currentTab !== "commands";
803
- document.getElementById("panel-release").hidden = currentTab !== "release";
804
- document.getElementById("panel-update").hidden = currentTab !== "update";
805
- document.getElementById("panel-runs").hidden = currentTab !== "runs";
806
- document.getElementById("panel-skills").hidden = currentTab !== "skills";
807
- document.getElementById("panel-settings").hidden = currentTab !== "settings";
808
- document.getElementById("panel-documents").hidden = currentTab !== "documents";
809
- renderChrome();
810
- }
811
-
812
- async function openMustflowFolder() {
813
- const response = await fetch("/api/open-mustflow", {
814
- method: "POST",
815
- headers: { "x-mustflow-dashboard-token": dashboardToken }
816
- });
817
- if (!response.ok) throw new Error(await response.text());
818
- statusKey("dashboard.ui.openedMustflow", "ok");
819
- }
820
-
821
- async function loadSnapshot() {
822
- const response = await fetch("/api/preferences", {
823
- headers: { "x-mustflow-dashboard-token": dashboardToken }
824
- });
825
- if (!response.ok) throw new Error(await response.text());
826
- snapshot = await response.json();
827
- pending = new Map();
828
- document.getElementById("save").disabled = true;
829
- statusKey("dashboard.ui.reloaded", "ok");
830
- render();
831
- }
832
-
833
- async function loadStatus() {
834
- const response = await fetch("/api/status", {
835
- headers: { "x-mustflow-dashboard-token": dashboardToken }
836
- });
837
- if (!response.ok) throw new Error(await response.text());
838
- dashboardStatus = await response.json();
839
- statusKey(
840
- currentTab === "commands"
841
- ? "dashboard.commands.reloaded"
842
- : currentTab === "verification"
843
- ? "dashboard.verification.reloaded"
844
- : currentTab === "release"
845
- ? "dashboard.release.reloaded"
846
- : currentTab === "update"
847
- ? "dashboard.update.reloaded"
848
- : currentTab === "runs"
849
- ? "dashboard.runs.reloaded"
850
- : currentTab === "skills"
851
- ? "dashboard.skills.reloaded"
852
- : "dashboard.status.reloaded",
853
- "ok"
854
- );
855
- renderStatusPanel();
856
- renderVerificationPanel();
857
- renderCommandPanel();
858
- renderReleasePanel();
859
- renderUpdatePanel();
860
- renderRunsPanel();
861
- renderSkillsPanel();
862
- }
863
-
864
- function docStatusQuery() {
865
- const value = document.getElementById("doc-status-filter").value;
866
- if (value === "active") return "";
867
- if (value === "all") return "?all=1";
868
- return "?status=" + encodeURIComponent(value);
869
- }
870
-
871
- function formatBoolean(value) {
872
- return message(value ? "dashboard.status.yes" : "dashboard.status.no");
873
- }
874
-
875
- function formatLatestRun(latestRun) {
876
- if (!latestRun.exists) return message("dashboard.status.latestRunMissing");
877
- if (!latestRun.valid) return message("dashboard.status.latestRunInvalid") + ": " + latestRun.error;
878
- const parts = [latestRun.intent, latestRun.status];
879
- if (latestRun.exit_code !== null) parts.push("exit " + latestRun.exit_code);
880
- if (latestRun.finished_at) parts.push(latestRun.finished_at);
881
- return parts.join(" / ");
882
- }
883
-
884
- function appendStatusItem(root, labelKey, value, tone = "") {
885
- const item = document.createElement("div");
886
- item.className = "status-item";
887
- const label = document.createElement("div");
888
- label.className = "status-label";
889
- label.textContent = message(labelKey);
890
- const content = document.createElement("div");
891
- content.className = tone ? "status-value " + tone : "status-value";
892
- content.textContent = value;
893
- item.appendChild(label);
894
- item.appendChild(content);
895
- root.appendChild(item);
896
- }
897
-
898
- function renderStatusPanel() {
899
- const root = document.getElementById("dashboard-status");
900
- root.textContent = "";
901
- const section = document.createElement("section");
902
- const heading = document.createElement("h2");
903
- heading.textContent = message("dashboard.status.overview");
904
- const grid = document.createElement("div");
905
- grid.className = "status-grid";
906
- const hasIssues = dashboardStatus.issues.length > 0 || dashboardStatus.changed_files.length > 0 || dashboardStatus.missing_files.length > 0;
907
- appendStatusItem(grid, "dashboard.status.installed", formatBoolean(dashboardStatus.installed), dashboardStatus.installed ? "ok" : "warn");
908
- appendStatusItem(grid, "dashboard.status.manifestLock", dashboardStatus.manifest_lock, dashboardStatus.manifest_lock === "present" ? "ok" : "warn");
909
- appendStatusItem(grid, "dashboard.status.template", dashboardStatus.template ? dashboardStatus.template.id + " " + dashboardStatus.template.version : message("value.none"));
910
- appendStatusItem(grid, "dashboard.status.trackedFiles", String(dashboardStatus.tracked_files));
911
- appendStatusItem(grid, "dashboard.status.changedFiles", String(dashboardStatus.changed_files.length), dashboardStatus.changed_files.length === 0 ? "ok" : "warn");
912
- appendStatusItem(grid, "dashboard.status.missingFiles", String(dashboardStatus.missing_files.length), dashboardStatus.missing_files.length === 0 ? "ok" : "warn");
913
- appendStatusItem(grid, "dashboard.status.runnableIntents", String(dashboardStatus.runnable_intents.length));
914
- appendStatusItem(grid, "dashboard.status.activeReviewDocuments", String(dashboardStatus.active_review_documents));
915
- appendStatusItem(grid, "dashboard.status.latestRun", formatLatestRun(dashboardStatus.latest_run), dashboardStatus.latest_run.exists && dashboardStatus.latest_run.valid ? "ok" : "");
916
- section.appendChild(heading);
917
- section.appendChild(grid);
918
- root.appendChild(section);
919
-
920
- const issuesSection = document.createElement("section");
921
- const issuesHeading = document.createElement("h2");
922
- issuesHeading.textContent = message("dashboard.status.issues");
923
- issuesSection.appendChild(issuesHeading);
924
- if (!hasIssues) {
925
- const empty = document.createElement("div");
926
- empty.className = "empty";
927
- empty.textContent = message("dashboard.status.noIssues");
928
- issuesSection.appendChild(empty);
929
- } else {
930
- const list = document.createElement("ul");
931
- list.className = "issue-list";
932
- for (const issue of dashboardStatus.issues) {
933
- const item = document.createElement("li");
934
- item.textContent = issue;
935
- list.appendChild(item);
936
- }
937
- for (const changed of dashboardStatus.changed_files) {
938
- const item = document.createElement("li");
939
- item.textContent = message("dashboard.status.changedFile") + ": " + changed;
940
- list.appendChild(item);
941
- }
942
- for (const missing of dashboardStatus.missing_files) {
943
- const item = document.createElement("li");
944
- item.textContent = message("dashboard.status.missingFile") + ": " + missing;
945
- list.appendChild(item);
946
- }
947
- issuesSection.appendChild(list);
948
- }
949
- root.appendChild(issuesSection);
950
- }
951
-
952
- async function copyVerificationCommand(command) {
953
- await navigator.clipboard.writeText(command);
954
- statusKey("dashboard.verification.copied", "ok");
955
- }
956
-
957
- async function copyVerificationPlan(commands) {
958
- await navigator.clipboard.writeText(commands.join("\\n"));
959
- statusKey("dashboard.verification.planCopied", "ok");
960
- }
961
-
962
- async function copyReleaseCommand(command) {
963
- await navigator.clipboard.writeText(command);
964
- statusKey("dashboard.release.copied", "ok");
965
- }
966
-
967
- async function copyUpdateCommand(command) {
968
- await navigator.clipboard.writeText(command);
969
- statusKey("dashboard.update.copied", "ok");
970
- }
971
-
972
- function appendVerificationFiles(root, files) {
973
- if (files.length === 0) return;
974
- const details = document.createElement("div");
975
- details.className = "verification-files";
976
- details.textContent = message("dashboard.verification.files") + ": " + files.join(", ");
977
- root.appendChild(details);
978
- }
979
-
980
- function renderVerificationPanel() {
981
- const root = document.getElementById("dashboard-verification");
982
- root.textContent = "";
983
- const verification = dashboardStatus.verification;
984
-
985
- const section = document.createElement("section");
986
- const heading = document.createElement("h2");
987
- heading.textContent = message("dashboard.verification.recommendations");
988
- section.appendChild(heading);
989
-
990
- if (verification.changed_files.length === 0) {
991
- const empty = document.createElement("div");
992
- empty.className = "empty";
993
- empty.textContent = message("dashboard.verification.empty");
994
- section.appendChild(empty);
995
- root.appendChild(section);
996
- return;
997
- }
998
-
999
- if (verification.recommendations.length === 0) {
1000
- const empty = document.createElement("div");
1001
- empty.className = "empty";
1002
- empty.textContent = message("dashboard.verification.none");
1003
- section.appendChild(empty);
1004
- root.appendChild(section);
1005
- return;
1006
- }
1007
-
1008
- for (const recommendation of verification.recommendations) {
1009
- const row = document.createElement("div");
1010
- row.className = "verification-row";
1011
- const summary = document.createElement("div");
1012
- const name = document.createElement("div");
1013
- name.className = "command-name";
1014
- name.textContent = recommendation.intent;
1015
- const state = document.createElement("div");
1016
- state.className = recommendation.runnable ? "command-state ok" : "command-state warn";
1017
- state.textContent = recommendation.runnable ? message("dashboard.commands.runnable") : message("dashboard.verification.unavailable");
1018
- summary.appendChild(name);
1019
- summary.appendChild(state);
1020
-
1021
- const details = document.createElement("div");
1022
- const command = document.createElement("div");
1023
- command.className = "verification-command";
1024
- command.textContent = recommendation.command;
1025
- const reason = document.createElement("div");
1026
- reason.className = "command-note";
1027
- reason.textContent = message(recommendation.reason_key);
1028
- details.appendChild(command);
1029
- details.appendChild(reason);
1030
- appendVerificationFiles(details, recommendation.files);
1031
-
1032
- const copy = document.createElement("button");
1033
- copy.type = "button";
1034
- copy.className = "verification-copy";
1035
- copy.textContent = message("dashboard.verification.copy");
1036
- copy.title = message("dashboard.verification.copy");
1037
- copy.setAttribute("aria-label", message("dashboard.verification.copy"));
1038
- copy.disabled = !recommendation.runnable;
1039
- copy.addEventListener("click", () => {
1040
- copyVerificationCommand(recommendation.command).catch((error) => statusText(error.message, "error"));
1041
- });
1042
-
1043
- row.appendChild(summary);
1044
- row.appendChild(details);
1045
- row.appendChild(copy);
1046
- section.appendChild(row);
1047
- }
1048
-
1049
- root.appendChild(section);
1050
-
1051
- if (verification.schedule.batches.length > 0) {
1052
- const scheduleSection = document.createElement("section");
1053
- const scheduleHeading = document.createElement("h2");
1054
- scheduleHeading.textContent = message("dashboard.verification.schedule");
1055
- scheduleSection.appendChild(scheduleHeading);
1056
- const entriesByIntent = new Map(verification.schedule.entries.map((entry) => [entry.intent, entry]));
1057
- const planCommands = verification.schedule.batches.flatMap((batch) => batch.commands);
1058
- for (const batch of verification.schedule.batches) {
1059
- const row = document.createElement("div");
1060
- row.className = "verification-row";
1061
- const summary = document.createElement("div");
1062
- const name = document.createElement("div");
1063
- name.className = "command-name";
1064
- name.textContent = message("dashboard.verification.batch") + " " + batch.index;
1065
- const state = document.createElement("div");
1066
- state.className = "command-state ok";
1067
- state.textContent = batch.locks.length > 0 ? message("dashboard.verification.locks") + ": " + batch.locks.join(", ") : message("dashboard.verification.noLocks");
1068
- summary.appendChild(name);
1069
- summary.appendChild(state);
1070
-
1071
- const details = document.createElement("div");
1072
- const commands = document.createElement("div");
1073
- commands.className = "verification-command";
1074
- commands.textContent = batch.commands.join(" -> ");
1075
- details.appendChild(commands);
1076
- for (const intent of batch.intents) {
1077
- const entry = entriesByIntent.get(intent);
1078
- if (!entry) continue;
1079
- const effects = document.createElement("div");
1080
- effects.className = "verification-files";
1081
- effects.textContent = message("dashboard.verification.effects") + ": " + entry.effects.map((effect) => effect.mode + " " + (effect.path || effect.lock) + " [" + effect.lock + "]").join(", ");
1082
- details.appendChild(effects);
1083
- if (entry.conflicts.length > 0) {
1084
- const conflicts = document.createElement("div");
1085
- conflicts.className = "command-note";
1086
- conflicts.textContent = message("dashboard.verification.conflicts") + ": " + entry.conflicts.map((conflict) => conflict.intent + " (" + conflict.lock + ")").join(", ");
1087
- details.appendChild(conflicts);
1088
- }
1089
- }
1090
-
1091
- const copy = document.createElement("button");
1092
- copy.type = "button";
1093
- copy.className = "verification-copy";
1094
- copy.textContent = message("dashboard.verification.copyPlan");
1095
- copy.title = message("dashboard.verification.copyPlan");
1096
- copy.setAttribute("aria-label", message("dashboard.verification.copyPlan"));
1097
- copy.disabled = planCommands.length === 0;
1098
- copy.addEventListener("click", () => {
1099
- copyVerificationPlan(planCommands).catch((error) => statusText(error.message, "error"));
1100
- });
1101
-
1102
- row.appendChild(summary);
1103
- row.appendChild(details);
1104
- row.appendChild(copy);
1105
- scheduleSection.appendChild(row);
1106
- }
1107
- root.appendChild(scheduleSection);
1108
- }
1109
-
1110
- if (verification.skipped.length > 0) {
1111
- const skippedSection = document.createElement("section");
1112
- const skippedHeading = document.createElement("h2");
1113
- skippedHeading.textContent = message("dashboard.verification.skipped");
1114
- skippedSection.appendChild(skippedHeading);
1115
- for (const skipped of verification.skipped) {
1116
- const row = document.createElement("div");
1117
- row.className = "command-note";
1118
- row.textContent = skipped.intent + ": " + message(skipped.reason_key);
1119
- skippedSection.appendChild(row);
1120
- }
1121
- root.appendChild(skippedSection);
1122
- }
1123
- }
1124
-
1125
- function appendCommandMeta(root, labelKey, value) {
1126
- if (value === null || value === undefined || value === "") return;
1127
- const item = document.createElement("span");
1128
- item.textContent = message(labelKey) + ": " + value;
1129
- root.appendChild(item);
1130
- }
1131
-
1132
- function commandStateKey(intent) {
1133
- if (intent.runnable) return "dashboard.commands.runnable";
1134
- if (intent.status === "manual_only") return "dashboard.commands.manualOnly";
1135
- if (intent.status === "unknown") return "dashboard.commands.unavailable";
1136
- return "dashboard.commands.blocked";
1137
- }
1138
-
1139
- function formatList(values) {
1140
- return values.length === 0 ? message("value.none") : values.join(", ");
1141
- }
1142
-
1143
- function formatCommandWriteLock(writeLock) {
1144
- const paths = writeLock.paths.length === 0 ? message("value.none") : writeLock.paths.join(", ");
1145
- return writeLock.lock + ": " + paths;
1146
- }
1147
-
1148
- function formatCommandLockConflict(conflict) {
1149
- const paths = conflict.conflicting_paths.length === 0 ? "" : " / " + conflict.conflicting_paths.join(", ");
1150
- return conflict.intent + " (" + conflict.lock + ")" + paths;
1151
- }
1152
-
1153
- function appendCommandEffectGraph(root, intent) {
1154
- const graph = intent.effect_graph;
1155
- if (!graph || graph.status !== "fresh") return;
1156
-
1157
- if (graph.write_locks.length > 0) {
1158
- const locks = document.createElement("div");
1159
- locks.className = "verification-files";
1160
- locks.textContent = message("dashboard.commands.effectGraph") + ": " + graph.write_locks.map(formatCommandWriteLock).join(", ");
1161
- root.appendChild(locks);
1162
- }
1163
-
1164
- if (graph.lock_conflicts.length > 0) {
1165
- const conflicts = document.createElement("div");
1166
- conflicts.className = "command-note";
1167
- conflicts.textContent = message("dashboard.verification.conflicts") + ": " + graph.lock_conflicts.map(formatCommandLockConflict).join(", ");
1168
- root.appendChild(conflicts);
1169
- }
1170
- }
1171
-
1172
- function renderCommandPanel() {
1173
- const root = document.getElementById("dashboard-commands");
1174
- root.textContent = "";
1175
- const section = document.createElement("section");
1176
- const heading = document.createElement("h2");
1177
- heading.textContent = message("dashboard.commands.heading");
1178
- section.appendChild(heading);
1179
-
1180
- if (!dashboardStatus.command_contract.exists || dashboardStatus.command_contract.intents.length === 0) {
1181
- const empty = document.createElement("div");
1182
- empty.className = "empty";
1183
- empty.textContent = message("dashboard.commands.empty");
1184
- section.appendChild(empty);
1185
- root.appendChild(section);
1186
- return;
1187
- }
1188
-
1189
- const graphStatus = dashboardStatus.command_contract.effect_graph_status;
1190
- if (graphStatus && graphStatus.status !== "fresh") {
1191
- const note = document.createElement("div");
1192
- note.className = "command-note";
1193
- note.textContent =
1194
- message("dashboard.commands.effectGraphUnavailable") +
1195
- ": " +
1196
- (graphStatus.refresh_hint || graphStatus.status);
1197
- section.appendChild(note);
1198
- }
1199
-
1200
- for (const intent of dashboardStatus.command_contract.intents) {
1201
- const row = document.createElement("div");
1202
- row.className = "command-row";
1203
- const summary = document.createElement("div");
1204
- const name = document.createElement("div");
1205
- name.className = "command-name";
1206
- name.textContent = intent.name;
1207
- const state = document.createElement("div");
1208
- state.className = intent.runnable ? "command-state ok" : "command-state warn";
1209
- state.textContent = message(commandStateKey(intent));
1210
- summary.appendChild(name);
1211
- summary.appendChild(state);
1212
-
1213
- const details = document.createElement("div");
1214
- const description = document.createElement("div");
1215
- description.className = "command-description";
1216
- description.textContent = intent.description || message("value.none");
1217
- const meta = document.createElement("div");
1218
- meta.className = "command-meta";
1219
- appendCommandMeta(meta, "dashboard.commands.status", intent.status);
1220
- appendCommandMeta(meta, "dashboard.commands.lifecycle", intent.lifecycle);
1221
- appendCommandMeta(meta, "dashboard.commands.runPolicy", intent.run_policy);
1222
- appendCommandMeta(meta, "dashboard.commands.stdin", intent.stdin);
1223
- appendCommandMeta(meta, "dashboard.commands.timeout", intent.timeout_seconds);
1224
- appendCommandMeta(meta, "dashboard.commands.cwd", intent.cwd);
1225
- appendCommandMeta(meta, "dashboard.commands.writes", formatList(intent.writes));
1226
- details.appendChild(description);
1227
- details.appendChild(meta);
1228
- if (intent.reason) {
1229
- const reason = document.createElement("div");
1230
- reason.className = "command-note";
1231
- reason.textContent = message("dashboard.commands.reason") + ": " + intent.reason;
1232
- details.appendChild(reason);
1233
- }
1234
- if (intent.agent_action) {
1235
- const action = document.createElement("div");
1236
- action.className = "command-note";
1237
- action.textContent = message("dashboard.commands.agentAction") + ": " + intent.agent_action;
1238
- details.appendChild(action);
1239
- }
1240
- appendCommandEffectGraph(details, intent);
1241
-
1242
- row.appendChild(summary);
1243
- row.appendChild(details);
1244
- section.appendChild(row);
1245
- }
1246
-
1247
- root.appendChild(section);
1248
- }
1249
-
1250
- function findIntent(name) {
1251
- return dashboardStatus.command_contract.intents.find((intent) => intent.name === name);
1252
- }
1253
-
1254
- function renderReleaseCommand(root, intentName, fallbackCommand, reasonKey) {
1255
- const intent = findIntent(intentName);
1256
- const row = document.createElement("div");
1257
- row.className = "verification-row";
1258
- const summary = document.createElement("div");
1259
- const name = document.createElement("div");
1260
- name.className = "command-name";
1261
- name.textContent = intentName;
1262
- const state = document.createElement("div");
1263
- const runnable = intentName === "version_check" ? true : intent ? intent.runnable : false;
1264
- state.className = runnable ? "command-state ok" : "command-state warn";
1265
- state.textContent = runnable ? message("dashboard.commands.runnable") : message("dashboard.verification.unavailable");
1266
- summary.appendChild(name);
1267
- summary.appendChild(state);
1268
-
1269
- const details = document.createElement("div");
1270
- const command = document.createElement("div");
1271
- command.className = "verification-command";
1272
- command.textContent = fallbackCommand;
1273
- const reason = document.createElement("div");
1274
- reason.className = "command-note";
1275
- reason.textContent = message(reasonKey);
1276
- details.appendChild(command);
1277
- details.appendChild(reason);
1278
-
1279
- const copy = document.createElement("button");
1280
- copy.type = "button";
1281
- copy.className = "verification-copy";
1282
- copy.textContent = message("dashboard.verification.copy");
1283
- copy.title = message("dashboard.verification.copy");
1284
- copy.setAttribute("aria-label", message("dashboard.verification.copy"));
1285
- copy.disabled = !runnable;
1286
- copy.addEventListener("click", () => {
1287
- copyReleaseCommand(fallbackCommand).catch((error) => statusText(error.message, "error"));
1288
- });
1289
-
1290
- row.appendChild(summary);
1291
- row.appendChild(details);
1292
- row.appendChild(copy);
1293
- root.appendChild(row);
1294
- }
1295
-
1296
- function renderReleasePanel() {
1297
- const root = document.getElementById("dashboard-release");
1298
- root.textContent = "";
1299
- const overview = document.createElement("section");
1300
- const overviewHeading = document.createElement("h2");
1301
- overviewHeading.textContent = message("dashboard.release.overview");
1302
- const grid = document.createElement("div");
1303
- grid.className = "status-grid";
1304
- appendStatusItem(grid, "dashboard.release.packageVersion", dashboardStatus.release.package_name + " " + dashboardStatus.release.package_version);
1305
- appendStatusItem(grid, "dashboard.release.templateVersion", dashboardStatus.template ? dashboardStatus.template.id + " " + dashboardStatus.template.version : message("value.none"));
1306
- appendStatusItem(grid, "dashboard.release.autoBump", formatBoolean(Boolean(settingValue("release.versioning.auto_bump"))), settingValue("release.versioning.auto_bump") ? "ok" : "");
1307
- appendStatusItem(grid, "dashboard.release.requireConfirmation", formatBoolean(Boolean(settingValue("release.versioning.require_user_confirmation"))));
1308
- appendStatusItem(grid, "dashboard.release.changedFiles", String(dashboardStatus.release.release_sensitive_changed_files.length), dashboardStatus.release.release_sensitive_changed_files.length === 0 ? "ok" : "warn");
1309
- overview.appendChild(overviewHeading);
1310
- overview.appendChild(grid);
1311
- root.appendChild(overview);
1312
-
1313
- const sources = document.createElement("section");
1314
- const sourcesHeading = document.createElement("h2");
1315
- sourcesHeading.textContent = message("dashboard.release.versionSources");
1316
- sources.appendChild(sourcesHeading);
1317
- if (dashboardStatus.release.version_sources.length === 0) {
1318
- const empty = document.createElement("div");
1319
- empty.className = "empty";
1320
- empty.textContent = message("dashboard.release.noVersionSources");
1321
- sources.appendChild(empty);
1322
- } else {
1323
- for (const source of dashboardStatus.release.version_sources) {
1324
- const row = document.createElement("div");
1325
- row.className = "command-row";
1326
- const summary = document.createElement("div");
1327
- const name = document.createElement("div");
1328
- name.className = "command-name";
1329
- name.textContent = source.path;
1330
- const state = document.createElement("div");
1331
- state.className = "command-state";
1332
- state.textContent = source.kind;
1333
- summary.appendChild(name);
1334
- summary.appendChild(state);
1335
- const details = document.createElement("div");
1336
- const meta = document.createElement("div");
1337
- meta.className = "command-meta";
1338
- appendCommandMeta(meta, "dashboard.release.declared", source.declared ? message("dashboard.status.yes") : message("dashboard.status.no"));
1339
- appendCommandMeta(meta, "dashboard.release.authority", source.authority || message("value.none"));
1340
- details.appendChild(meta);
1341
- row.appendChild(summary);
1342
- row.appendChild(details);
1343
- sources.appendChild(row);
1344
- }
1345
- }
1346
- root.appendChild(sources);
1347
-
1348
- const changed = document.createElement("section");
1349
- const changedHeading = document.createElement("h2");
1350
- changedHeading.textContent = message("dashboard.release.changedFiles");
1351
- changed.appendChild(changedHeading);
1352
- if (dashboardStatus.release.release_sensitive_changed_files.length === 0) {
1353
- const empty = document.createElement("div");
1354
- empty.className = "empty";
1355
- empty.textContent = message("dashboard.release.noChangedFiles");
1356
- changed.appendChild(empty);
1357
- } else {
1358
- const list = document.createElement("ul");
1359
- list.className = "issue-list";
1360
- for (const file of dashboardStatus.release.release_sensitive_changed_files) {
1361
- const item = document.createElement("li");
1362
- item.textContent = file;
1363
- list.appendChild(item);
1364
- }
1365
- changed.appendChild(list);
1366
- }
1367
- root.appendChild(changed);
1368
-
1369
- const commands = document.createElement("section");
1370
- const commandsHeading = document.createElement("h2");
1371
- commandsHeading.textContent = message("dashboard.release.commands");
1372
- commands.appendChild(commandsHeading);
1373
- renderReleaseCommand(commands, "version_check", "mf version --check", "dashboard.release.reason.versionCheck");
1374
- renderReleaseCommand(commands, "test_release", "mf run test_release", "dashboard.release.reason.testRelease");
1375
- renderReleaseCommand(commands, "docs_validate", "mf run docs_validate", "dashboard.release.reason.docsValidate");
1376
- root.appendChild(commands);
1377
- }
1378
-
1379
- function renderUpdateCommand(root, command, labelKey, reasonKey, enabled = true) {
1380
- const row = document.createElement("div");
1381
- row.className = "verification-row";
1382
- const summary = document.createElement("div");
1383
- const name = document.createElement("div");
1384
- name.className = "command-name";
1385
- name.textContent = message(labelKey);
1386
- const state = document.createElement("div");
1387
- state.className = enabled ? "command-state ok" : "command-state warn";
1388
- state.textContent = enabled ? message("dashboard.commands.runnable") : message("dashboard.update.blocked");
1389
- summary.appendChild(name);
1390
- summary.appendChild(state);
1391
-
1392
- const details = document.createElement("div");
1393
- const commandText = document.createElement("div");
1394
- commandText.className = "verification-command";
1395
- commandText.textContent = command;
1396
- const reason = document.createElement("div");
1397
- reason.className = "command-note";
1398
- reason.textContent = message(reasonKey);
1399
- details.appendChild(commandText);
1400
- details.appendChild(reason);
1401
-
1402
- const copy = document.createElement("button");
1403
- copy.type = "button";
1404
- copy.className = "verification-copy";
1405
- copy.textContent = message("dashboard.verification.copy");
1406
- copy.title = message("dashboard.verification.copy");
1407
- copy.setAttribute("aria-label", message("dashboard.verification.copy"));
1408
- copy.disabled = !enabled;
1409
- copy.addEventListener("click", () => {
1410
- copyUpdateCommand(command).catch((error) => statusText(error.message, "error"));
1411
- });
1412
-
1413
- row.appendChild(summary);
1414
- row.appendChild(details);
1415
- row.appendChild(copy);
1416
- root.appendChild(row);
1417
- }
1418
-
1419
- function renderUpdateItem(root, item) {
1420
- const row = document.createElement("div");
1421
- row.className = "command-row";
1422
- const summary = document.createElement("div");
1423
- const name = document.createElement("div");
1424
- name.className = "command-name";
1425
- name.textContent = item.relativePath;
1426
- const state = document.createElement("div");
1427
- state.className = item.action === "create" || item.action === "update" ? "command-state ok" : "command-state warn";
1428
- state.textContent = message("dashboard.update.action." + item.action);
1429
- summary.appendChild(name);
1430
- summary.appendChild(state);
1431
-
1432
- const details = document.createElement("div");
1433
- const meta = document.createElement("div");
1434
- meta.className = "command-meta";
1435
- appendCommandMeta(meta, "dashboard.update.source", item.sourceKind);
1436
- const reason = document.createElement("div");
1437
- reason.className = "command-note";
1438
- reason.textContent = message("dashboard.update.reason") + ": " + item.reason;
1439
- details.appendChild(meta);
1440
- details.appendChild(reason);
1441
-
1442
- row.appendChild(summary);
1443
- row.appendChild(details);
1444
- root.appendChild(row);
1445
- }
1446
-
1447
- function renderUpdateItemList(root, titleKey, emptyKey, items) {
1448
- const section = document.createElement("section");
1449
- const heading = document.createElement("h2");
1450
- heading.textContent = message(titleKey);
1451
- section.appendChild(heading);
1452
- if (items.length === 0) {
1453
- const empty = document.createElement("div");
1454
- empty.className = "empty";
1455
- empty.textContent = message(emptyKey);
1456
- section.appendChild(empty);
1457
- } else {
1458
- for (const item of items) renderUpdateItem(section, item);
1459
- }
1460
- root.appendChild(section);
1461
- }
1462
-
1463
- function renderUpdatePanel() {
1464
- const root = document.getElementById("dashboard-update");
1465
- root.textContent = "";
1466
- const update = dashboardStatus.update;
1467
-
1468
- const overview = document.createElement("section");
1469
- const heading = document.createElement("h2");
1470
- heading.textContent = message("dashboard.update.overview");
1471
- const grid = document.createElement("div");
1472
- grid.className = "status-grid";
1473
- appendStatusItem(grid, "dashboard.update.dryRun", update.ok ? message("dashboard.status.yes") : message("dashboard.status.no"), update.ok ? "ok" : "warn");
1474
- appendStatusItem(grid, "dashboard.update.applyReady", update.apply_ready ? message("dashboard.status.yes") : message("dashboard.status.no"), update.apply_ready ? "ok" : "warn");
1475
- appendStatusItem(grid, "dashboard.update.wouldUpdate", String(update.summary.wouldUpdate));
1476
- appendStatusItem(grid, "dashboard.update.wouldCreate", String(update.summary.wouldCreate));
1477
- appendStatusItem(grid, "dashboard.update.blockedLocalChanges", String(update.summary.blockedLocalChanges), update.summary.blockedLocalChanges === 0 ? "ok" : "warn");
1478
- appendStatusItem(grid, "dashboard.update.manualReview", String(update.summary.manualReview), update.summary.manualReview === 0 ? "ok" : "warn");
1479
- appendStatusItem(grid, "dashboard.update.unchanged", String(update.summary.unchanged));
1480
- overview.appendChild(heading);
1481
- overview.appendChild(grid);
1482
- if (update.error) {
1483
- const error = document.createElement("div");
1484
- error.className = "command-note";
1485
- error.textContent = message("dashboard.update.error") + ": " + update.error;
1486
- overview.appendChild(error);
1487
- }
1488
- root.appendChild(overview);
1489
-
1490
- const commands = document.createElement("section");
1491
- const commandsHeading = document.createElement("h2");
1492
- commandsHeading.textContent = message("dashboard.update.commands");
1493
- commands.appendChild(commandsHeading);
1494
- renderUpdateCommand(commands, update.dry_run_command, "dashboard.update.command.dryRun", "dashboard.update.reason.dryRun", update.ok);
1495
- renderUpdateCommand(commands, update.apply_command, "dashboard.update.command.apply", "dashboard.update.reason.apply", update.ok && update.apply_ready);
1496
- root.appendChild(commands);
1497
-
1498
- renderUpdateItemList(root, "dashboard.update.blockers", "dashboard.update.noBlockers", update.blockers);
1499
- renderUpdateItemList(root, "dashboard.update.changes", "dashboard.update.noChanges", update.changes);
1500
- }
1501
-
1502
- function formatDuration(value) {
1503
- if (typeof value !== "number") return message("value.none");
1504
- if (value < 1000) return String(value) + " ms";
1505
- return (value / 1000).toFixed(2) + " s";
1506
- }
1507
-
1508
- function renderRunOutput(root, titleKey, output) {
1509
- const section = document.createElement("section");
1510
- const heading = document.createElement("h2");
1511
- heading.textContent = message(titleKey);
1512
- section.appendChild(heading);
1513
- const meta = document.createElement("div");
1514
- meta.className = "command-meta";
1515
- appendCommandMeta(meta, "dashboard.runs.bytes", output.bytes);
1516
- appendCommandMeta(meta, "dashboard.runs.truncated", formatBoolean(output.truncated));
1517
- section.appendChild(meta);
1518
- if (output.tail) {
1519
- const pre = document.createElement("pre");
1520
- pre.className = "doc-comment";
1521
- pre.textContent = output.tail;
1522
- section.appendChild(pre);
1523
- } else {
1524
- const empty = document.createElement("div");
1525
- empty.className = "empty";
1526
- empty.textContent = message("dashboard.runs.emptyOutput");
1527
- section.appendChild(empty);
1528
- }
1529
- root.appendChild(section);
1530
- }
1531
-
1532
- function renderRunsPanel() {
1533
- const root = document.getElementById("dashboard-runs");
1534
- root.textContent = "";
1535
- const run = dashboardStatus.run_history;
1536
-
1537
- const overview = document.createElement("section");
1538
- const heading = document.createElement("h2");
1539
- heading.textContent = message("dashboard.runs.heading");
1540
- overview.appendChild(heading);
1541
-
1542
- if (!run.exists) {
1543
- const empty = document.createElement("div");
1544
- empty.className = "empty";
1545
- empty.textContent = message("dashboard.runs.empty");
1546
- overview.appendChild(empty);
1547
- root.appendChild(overview);
1548
- return;
1549
- }
1550
-
1551
- if (!run.valid) {
1552
- const error = document.createElement("div");
1553
- error.className = "command-note";
1554
- error.textContent = message("dashboard.runs.invalid") + ": " + run.error;
1555
- overview.appendChild(error);
1556
- root.appendChild(overview);
1557
- return;
1558
- }
1559
-
1560
- const grid = document.createElement("div");
1561
- grid.className = "status-grid";
1562
- appendStatusItem(grid, "dashboard.runs.intent", run.intent);
1563
- appendStatusItem(grid, "dashboard.runs.status", run.status, run.status === "passed" ? "ok" : "warn");
1564
- appendStatusItem(grid, "dashboard.runs.exitCode", run.exit_code === null ? message("value.none") : String(run.exit_code));
1565
- appendStatusItem(grid, "dashboard.runs.timedOut", formatBoolean(run.timed_out), run.timed_out ? "warn" : "ok");
1566
- appendStatusItem(grid, "dashboard.runs.startedAt", run.started_at || message("value.none"));
1567
- appendStatusItem(grid, "dashboard.runs.finishedAt", run.finished_at || message("value.none"));
1568
- appendStatusItem(grid, "dashboard.runs.duration", formatDuration(run.duration_ms));
1569
- appendStatusItem(grid, "dashboard.runs.cwd", run.cwd || message("value.none"));
1570
- appendStatusItem(grid, "dashboard.runs.mode", run.mode || message("value.none"));
1571
- appendStatusItem(grid, "dashboard.runs.timeout", String(run.timeout_seconds));
1572
- appendStatusItem(grid, "dashboard.runs.receiptPath", run.receipt_path || run.path);
1573
- overview.appendChild(grid);
1574
-
1575
- const meta = document.createElement("div");
1576
- meta.className = "command-meta";
1577
- appendCommandMeta(meta, "dashboard.runs.lifecycle", run.lifecycle);
1578
- appendCommandMeta(meta, "dashboard.runs.runPolicy", run.run_policy);
1579
- appendCommandMeta(meta, "dashboard.runs.successExitCodes", formatList(run.success_exit_codes.map(String)));
1580
- appendCommandMeta(meta, "dashboard.runs.signal", run.signal || message("value.none"));
1581
- appendCommandMeta(meta, "dashboard.runs.killMethod", run.kill_method || message("value.none"));
1582
- overview.appendChild(meta);
1583
-
1584
- if (run.command_line.length > 0) {
1585
- const command = document.createElement("div");
1586
- command.className = "verification-command";
1587
- command.textContent = run.command_line.join(" ");
1588
- overview.appendChild(command);
1589
- }
1590
-
1591
- if (run.error) {
1592
- const error = document.createElement("div");
1593
- error.className = "command-note";
1594
- error.textContent = message("dashboard.runs.error") + ": " + run.error;
1595
- overview.appendChild(error);
1596
- }
1597
-
1598
- root.appendChild(overview);
1599
- renderRunOutput(root, "dashboard.runs.stdout", run.stdout);
1600
- renderRunOutput(root, "dashboard.runs.stderr", run.stderr);
1601
- }
1602
-
1603
- function skillAlignmentKey(route) {
1604
- if (!route.exists) return "dashboard.skills.missing";
1605
- return route.aligned ? "dashboard.skills.aligned" : "dashboard.skills.mismatch";
1606
- }
1607
-
1608
- function renderSkillsPanel() {
1609
- const root = document.getElementById("dashboard-skills");
1610
- root.textContent = "";
1611
- const overview = document.createElement("section");
1612
- const heading = document.createElement("h2");
1613
- heading.textContent = message("dashboard.skills.heading");
1614
- const grid = document.createElement("div");
1615
- grid.className = "status-grid";
1616
- appendStatusItem(grid, "dashboard.skills.indexPath", dashboardStatus.skills.index_path);
1617
- appendStatusItem(grid, "dashboard.skills.routes", String(dashboardStatus.skills.count));
1618
- overview.appendChild(heading);
1619
- overview.appendChild(grid);
1620
- root.appendChild(overview);
1621
-
1622
- const section = document.createElement("section");
1623
- const routesHeading = document.createElement("h2");
1624
- routesHeading.textContent = message("dashboard.skills.routes");
1625
- section.appendChild(routesHeading);
1626
-
1627
- if (!dashboardStatus.skills.exists || dashboardStatus.skills.routes.length === 0) {
1628
- const empty = document.createElement("div");
1629
- empty.className = "empty";
1630
- empty.textContent = message("dashboard.skills.empty");
1631
- section.appendChild(empty);
1632
- root.appendChild(section);
1633
- return;
1634
- }
1635
-
1636
- for (const route of dashboardStatus.skills.routes) {
1637
- const row = document.createElement("div");
1638
- row.className = "command-row";
1639
- const summary = document.createElement("div");
1640
- const name = document.createElement("div");
1641
- name.className = "command-name";
1642
- name.textContent = route.skill;
1643
- const state = document.createElement("div");
1644
- state.className = route.exists && route.aligned ? "command-state ok" : "command-state warn";
1645
- state.textContent = message(skillAlignmentKey(route));
1646
- summary.appendChild(name);
1647
- summary.appendChild(state);
1648
-
1649
- const details = document.createElement("div");
1650
- const trigger = document.createElement("div");
1651
- trigger.className = "command-description";
1652
- trigger.textContent = route.trigger;
1653
- const meta = document.createElement("div");
1654
- meta.className = "command-meta";
1655
- appendCommandMeta(meta, "dashboard.skills.path", route.skill_path);
1656
- appendCommandMeta(meta, "dashboard.skills.requiredInput", route.required_input);
1657
- appendCommandMeta(meta, "dashboard.skills.editScope", route.edit_scope);
1658
- appendCommandMeta(meta, "dashboard.skills.risk", route.risk);
1659
- appendCommandMeta(meta, "dashboard.skills.verificationIntents", formatList(route.verification_intents));
1660
- appendCommandMeta(meta, "dashboard.skills.declaredCommandIntents", formatList(route.declared_command_intents));
1661
- details.appendChild(trigger);
1662
- details.appendChild(meta);
1663
- if (route.expected_output) {
1664
- const output = document.createElement("div");
1665
- output.className = "command-note";
1666
- output.textContent = message("dashboard.skills.expectedOutput") + ": " + route.expected_output;
1667
- details.appendChild(output);
1668
- }
1669
-
1670
- row.appendChild(summary);
1671
- row.appendChild(details);
1672
- section.appendChild(row);
1673
- }
1674
-
1675
- root.appendChild(section);
1676
- }
1677
-
1678
- async function loadDocuments() {
1679
- const response = await fetch("/api/docs/review" + docStatusQuery(), {
1680
- headers: { "x-mustflow-dashboard-token": dashboardToken }
1681
- });
1682
- if (!response.ok) throw new Error(await response.text());
1683
- docReview = await response.json();
1684
- statusKey("dashboard.docs.reloaded", "ok");
1685
- renderChrome();
1686
- renderDocuments();
1687
- }
1688
-
1689
- async function save() {
1690
- const updates = Array.from(pending, ([id, value]) => ({ id, value }));
1691
- const response = await fetch("/api/preferences", {
1692
- method: "POST",
1693
- headers: {
1694
- "content-type": "application/json",
1695
- "x-mustflow-dashboard-token": dashboardToken
1696
- },
1697
- body: JSON.stringify({ updates })
1698
- });
1699
- if (!response.ok) throw new Error(await response.text());
1700
- snapshot = await response.json();
1701
- pending = new Map();
1702
- document.getElementById("save").disabled = true;
1703
- statusKey("dashboard.ui.saved", "ok");
1704
- render();
1705
- }
1706
-
1707
- async function markDocument(path, status) {
1708
- const reviewerId = document.getElementById("doc-reviewer-id").value.trim();
1709
- if (!reviewerId) {
1710
- statusKey("dashboard.docs.missingReviewerId", "error");
1711
- return;
1712
- }
1713
-
1714
- const response = await fetch("/api/docs/review" + docStatusQuery(), {
1715
- method: "POST",
1716
- headers: {
1717
- "content-type": "application/json",
1718
- "x-mustflow-dashboard-token": dashboardToken
1719
- },
1720
- body: JSON.stringify({
1721
- path,
1722
- status,
1723
- reviewerKind: document.getElementById("doc-reviewer-kind").value,
1724
- reviewerId,
1725
- summary: document.getElementById("doc-review-summary").value.trim()
1726
- })
1727
- });
1728
- if (!response.ok) throw new Error(await response.text());
1729
- docReview = await response.json();
1730
- statusKey("dashboard.docs.updated", "ok");
1731
- renderChrome();
1732
- renderDocuments();
1733
- }
1734
-
1735
- function renderDocFilters() {
1736
- const statusSelect = document.getElementById("doc-status-filter");
1737
- const currentStatus = statusSelect.value || "active";
1738
- statusSelect.textContent = "";
1739
- for (const value of docStatusFilters) {
1740
- const option = document.createElement("option");
1741
- option.value = value;
1742
- option.textContent = message("dashboard.docs.filter." + value);
1743
- option.selected = value === currentStatus;
1744
- statusSelect.appendChild(option);
1745
- }
1746
-
1747
- const kindSelect = document.getElementById("doc-reviewer-kind");
1748
- const currentKind = kindSelect.value || "human";
1749
- kindSelect.textContent = "";
1750
- for (const value of reviewerKinds) {
1751
- const option = document.createElement("option");
1752
- option.value = value;
1753
- option.textContent = message("dashboard.docs.reviewerKind." + value);
1754
- option.selected = value === currentKind;
1755
- kindSelect.appendChild(option);
1756
- }
1757
- }
1758
-
1759
- function documentMatchesPathFilter(entry, query) {
1760
- const normalizedQuery = query.trim().toLowerCase();
1761
- if (!normalizedQuery) return true;
1762
- const path = String(entry.path || "");
1763
- const fileName = path.split(/[\\\\/]/u).pop() || path;
1764
- return path.toLowerCase().includes(normalizedQuery) || fileName.toLowerCase().includes(normalizedQuery);
1765
- }
1766
-
1767
- function currentReviewerId() {
1768
- return document.getElementById("doc-reviewer-id").value.trim();
1769
- }
1770
-
1771
- function renderDocuments() {
1772
- const root = document.getElementById("docs-review-list");
1773
- root.textContent = "";
1774
-
1775
- if (docReview.documents.length === 0) {
1776
- const empty = document.createElement("div");
1777
- empty.className = "empty";
1778
- empty.textContent = message("dashboard.docs.empty");
1779
- root.appendChild(empty);
1780
- return;
1781
- }
1782
-
1783
- const pathFilter = document.getElementById("doc-path-filter").value;
1784
- const documents = docReview.documents.filter((entry) => documentMatchesPathFilter(entry, pathFilter));
1785
- if (documents.length === 0) {
1786
- const empty = document.createElement("div");
1787
- empty.className = "empty";
1788
- empty.textContent = message("dashboard.docs.noSearchMatches");
1789
- root.appendChild(empty);
1790
- return;
1791
- }
1792
-
1793
- for (const entry of documents) {
1794
- const row = document.createElement("div");
1795
- row.className = "doc-row";
1796
- const details = document.createElement("div");
1797
- const docPath = document.createElement("div");
1798
- docPath.className = "doc-path";
1799
- docPath.textContent = entry.path;
1800
- const meta = document.createElement("div");
1801
- meta.className = "doc-meta";
1802
- meta.textContent = entry.reason;
1803
- details.appendChild(docPath);
1804
- details.appendChild(meta);
1805
- if (entry.review_comment) {
1806
- const comment = document.createElement("pre");
1807
- comment.className = "doc-comment";
1808
- comment.textContent = message("dashboard.docs.comment") + ":\\n" + entry.review_comment;
1809
- details.appendChild(comment);
1810
- }
1811
-
1812
- const status = document.createElement("div");
1813
- status.className = "doc-status";
1814
- status.textContent = message("dashboard.docs.status." + entry.status);
1815
-
1816
- const actions = document.createElement("div");
1817
- actions.className = "doc-actions";
1818
- const reviewerIdMissing = currentReviewerId().length === 0;
1819
- for (const [nextStatus, labelKey, tooltipKey] of [
1820
- ["approved", "dashboard.docs.action.approve", "dashboard.docs.action.approve.tooltip"],
1821
- ["needs_human", "dashboard.docs.action.needsReview", "dashboard.docs.action.needsReview.tooltip"],
1822
- ["ignored", "dashboard.docs.action.ignore", "dashboard.docs.action.ignore.tooltip"]
1823
- ]) {
1824
- const button = document.createElement("button");
1825
- button.type = "button";
1826
- button.textContent = message(labelKey);
1827
- const actionLabel = reviewerIdMissing ? message("dashboard.docs.missingReviewerId") : message(tooltipKey);
1828
- button.title = actionLabel;
1829
- button.setAttribute("aria-label", actionLabel);
1830
- button.disabled = reviewerIdMissing || entry.status === nextStatus;
1831
- button.addEventListener("click", () => {
1832
- markDocument(entry.path, nextStatus).catch((error) => statusText(error.message, "error"));
1833
- });
1834
- actions.appendChild(button);
1835
- }
1836
-
1837
- row.appendChild(details);
1838
- row.appendChild(status);
1839
- row.appendChild(actions);
1840
- root.appendChild(row);
1841
- }
1842
- }
1843
-
1844
- document.getElementById("dashboard-language").addEventListener("change", (event) => {
1845
- currentLocale = event.target.value;
1846
- window.localStorage.setItem("mustflow.dashboard.language", currentLocale);
1847
- renderLocaleSelector();
1848
- renderChrome();
1849
- renderDocFilters();
1850
- renderStatusPanel();
1851
- renderVerificationPanel();
1852
- renderCommandPanel();
1853
- renderReleasePanel();
1854
- renderUpdatePanel();
1855
- renderRunsPanel();
1856
- renderSkillsPanel();
1857
- render();
1858
- renderDocuments();
1859
- });
1860
-
1861
- document.getElementById("reload").addEventListener("click", () => {
1862
- const action = currentTab === "documents" ? loadDocuments() : currentTab === "settings" ? loadSnapshot() : loadStatus();
1863
- action.catch((error) => statusText(error.message, "error"));
1864
- });
1865
- document.getElementById("save").addEventListener("click", () => {
1866
- save().catch((error) => statusText(error.message, "error"));
1867
- });
1868
- document.getElementById("open-mustflow").addEventListener("click", () => {
1869
- openMustflowFolder().catch((error) => statusText(error.message, "error"));
1870
- });
1871
- document.getElementById("doc-status-filter").addEventListener("change", () => {
1872
- loadDocuments().catch((error) => statusText(error.message, "error"));
1873
- });
1874
- document.getElementById("doc-path-filter").addEventListener("input", () => {
1875
- renderDocuments();
1876
- });
1877
- document.getElementById("doc-reviewer-id").addEventListener("input", () => {
1878
- renderDocuments();
1879
- });
1880
- for (const tab of document.querySelectorAll(".tab")) {
1881
- tab.addEventListener("click", () => {
1882
- currentTab = tab.dataset.tab;
1883
- renderTabState();
1884
- if (currentTab === "documents") {
1885
- loadDocuments().catch((error) => statusText(error.message, "error"));
1886
- } else if (currentTab === "status" || currentTab === "verification" || currentTab === "commands" || currentTab === "release" || currentTab === "update" || currentTab === "runs" || currentTab === "skills") {
1887
- loadStatus().catch((error) => statusText(error.message, "error"));
1888
- }
1889
- });
1890
- }
1891
- renderLocaleSelector();
1892
- renderDocFilters();
1893
- renderChrome();
1894
- renderTabState();
1895
- renderStatusPanel();
1896
- renderVerificationPanel();
1897
- renderCommandPanel();
1898
- renderReleasePanel();
1899
- renderUpdatePanel();
1900
- renderRunsPanel();
1901
- renderSkillsPanel();
1902
- render();
1903
- renderDocuments();
1904
- </script>
1905
- </body>
1906
- </html>`;
1907
- }
1
+ export { renderDashboardHtml } from './dashboard-html/template.js';