@crewhaus/hangar-ui 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -8
- package/assets/hangar.css +175 -0
- package/assets/js/api.js +2 -0
- package/assets/js/app.js +7 -1
- package/assets/js/dom.js +30 -0
- package/assets/js/router.js +9 -0
- package/assets/js/routes.js +66 -2
- package/assets/js/util.js +1 -0
- package/assets/js/views/advisor.js +631 -0
- package/assets/js/views/library.js +139 -11
- package/dist/embed-suite.d.ts +1 -0
- package/dist/embed-suite.js +289 -0
- package/dist/index.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -285,16 +285,60 @@ origin — never part of this one. The asset-hygiene suite pins the exception
|
|
|
285
285
|
to that single module, and `paneSandbox` strips `allow-same-origin` even if a
|
|
286
286
|
payload asks for it.
|
|
287
287
|
|
|
288
|
+
## M5 — the Advisor
|
|
289
|
+
|
|
290
|
+
`views/advisor.js` draws one more harness tab (`#/h/<id>/advisor`, right
|
|
291
|
+
after Overview — the strip order is the triage order) and one more fleet
|
|
292
|
+
screen (`#/advisor`):
|
|
293
|
+
|
|
294
|
+
- **The feed** — every alert, suggestion and optimization signal the manager
|
|
295
|
+
can derive about the harness, in one severity-ranked list. Every item
|
|
296
|
+
carries a hover TOOLTIP (`withTip`/`tipIcon` in `dom.js`: a `data-tip`
|
|
297
|
+
attribute the stylesheet renders via `attr()` — text only, so the
|
|
298
|
+
injection ban holds) explaining why it matters, the fact it was derived
|
|
299
|
+
from, guidance on what to do, and a quick action: an executable button for
|
|
300
|
+
a queued CLI verb (with its CLI twin beside it) or a deep link into the
|
|
301
|
+
tab that owns the fix. Acting opens an inline confirm where a COMMENT can
|
|
302
|
+
be attached — it is recorded in the harness-local decisions ledger next to
|
|
303
|
+
the queued job. Dismissing REQUIRES a reason (the server refuses without
|
|
304
|
+
one), dismissed items keep their recorded reasoning in a collapsible fold,
|
|
305
|
+
and every dismissal can be reopened. Zero open items renders as a
|
|
306
|
+
first-class "running optimally" state, never as an empty screen.
|
|
307
|
+
- **The trend** — "is it improving?": the eval pass-rate bars, the decision
|
|
308
|
+
counts, and a one-line verdict, all folded from durable sources.
|
|
309
|
+
- **Reports** — generate model-usage / costs / usefulness / optimization
|
|
310
|
+
reports (each button tooltipped with what it measures); saved reports are
|
|
311
|
+
listed and re-readable in place.
|
|
312
|
+
- **Issues** — describe a problem and it is tuned into an update ready to
|
|
313
|
+
run: the kind selector (tooltipped per kind) picks what gets queued, with
|
|
314
|
+
`optimize` — the eval→patch loop whose artifact is a reviewable spec
|
|
315
|
+
patch — as the default.
|
|
316
|
+
- **The fleet board** (`#/advisor`) — every harness's rollup, worst first,
|
|
317
|
+
each row deep-linking its Advisor tab.
|
|
318
|
+
|
|
319
|
+
The Library also becomes curatable (M5): harness rows carry a `hidden` flag
|
|
320
|
+
(the row editor's Visibility checkbox), hidden rows fold out of every view
|
|
321
|
+
except an explicit **Hidden** rail bucket, and a **Find harnesses…** panel
|
|
322
|
+
lists the unregistered harnesses under the scan roots (via
|
|
323
|
+
`GET /api/registry/discover`) with per-candidate Add buttons — discovery
|
|
324
|
+
without registration, so the Library only shows what you add. The blanket
|
|
325
|
+
Scan (which registers everything) stays as the labeled bulk fallback.
|
|
326
|
+
|
|
288
327
|
## Testing
|
|
289
328
|
|
|
290
|
-
`bun test
|
|
291
|
-
for the pure browser modules
|
|
292
|
-
`
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
bun
|
|
296
|
-
|
|
297
|
-
|
|
329
|
+
`bun run test` — two `bun test` invocations, DELIBERATELY two processes:
|
|
330
|
+
`bun test src` runs the unit tests for the pure browser modules
|
|
331
|
+
(`util.js`, `supervision.js`, `markdown.js`, `router.js`, `routes.js`,
|
|
332
|
+
`shapes.js`, the view decisions) and the `api.js` client under a stubbed
|
|
333
|
+
`fetch` (every wrapper asserted against the route map's method/path/body);
|
|
334
|
+
`bun test ./src/embed-suite.ts` then runs the embed-map
|
|
335
|
+
completeness/hygiene suite alone. The split is load-bearing, not taste:
|
|
336
|
+
the embed suite imports every asset as TEXT while the view tests import
|
|
337
|
+
the same files as ES modules, and bun's module registry keys a module by
|
|
338
|
+
path alone — in one process whichever load lands first poisons the other.
|
|
339
|
+
All DOM-free at import time and imported directly by bun. Deterministic:
|
|
340
|
+
clocks are injected, no network, no subprocesses. The server side of the
|
|
341
|
+
same contract runs in `@crewhaus/hangar-server`'s `contract.test.ts`.
|
|
298
342
|
|
|
299
343
|
The M2 screens stay testable without a browser by keeping the decisions —
|
|
300
344
|
state → row, envelope → disabled-with-reason, SSE frame → feed item,
|
package/assets/hangar.css
CHANGED
|
@@ -1724,3 +1724,178 @@ tr.deflated td {
|
|
|
1724
1724
|
grid-column: 2 / 3;
|
|
1725
1725
|
}
|
|
1726
1726
|
}
|
|
1727
|
+
|
|
1728
|
+
/* ── tooltips (M5): data-tip rendered via attr() — text only, never markup */
|
|
1729
|
+
|
|
1730
|
+
.has-tip {
|
|
1731
|
+
position: relative;
|
|
1732
|
+
cursor: help;
|
|
1733
|
+
}
|
|
1734
|
+
.has-tip::after {
|
|
1735
|
+
content: attr(data-tip);
|
|
1736
|
+
position: absolute;
|
|
1737
|
+
left: 0;
|
|
1738
|
+
bottom: calc(100% + 7px);
|
|
1739
|
+
z-index: 60;
|
|
1740
|
+
width: max-content;
|
|
1741
|
+
max-width: min(34rem, 78vw);
|
|
1742
|
+
padding: 7px 10px;
|
|
1743
|
+
border: 1px solid var(--rule-2);
|
|
1744
|
+
border-radius: var(--radius-sm);
|
|
1745
|
+
background: var(--panel-3);
|
|
1746
|
+
color: var(--ink);
|
|
1747
|
+
font-size: 12.5px;
|
|
1748
|
+
font-weight: 400;
|
|
1749
|
+
font-family: var(--sans);
|
|
1750
|
+
line-height: 1.45;
|
|
1751
|
+
white-space: normal;
|
|
1752
|
+
text-align: left;
|
|
1753
|
+
opacity: 0;
|
|
1754
|
+
pointer-events: none;
|
|
1755
|
+
transition: opacity 120ms ease;
|
|
1756
|
+
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
|
|
1757
|
+
}
|
|
1758
|
+
.has-tip:hover::after,
|
|
1759
|
+
.has-tip:focus-visible::after {
|
|
1760
|
+
opacity: 1;
|
|
1761
|
+
}
|
|
1762
|
+
.tip-icon {
|
|
1763
|
+
display: inline-flex;
|
|
1764
|
+
align-items: center;
|
|
1765
|
+
justify-content: center;
|
|
1766
|
+
width: 15px;
|
|
1767
|
+
height: 15px;
|
|
1768
|
+
border: 1px solid var(--rule-2);
|
|
1769
|
+
border-radius: 50%;
|
|
1770
|
+
font-size: 10.5px;
|
|
1771
|
+
color: var(--ink-3);
|
|
1772
|
+
background: var(--panel-2);
|
|
1773
|
+
vertical-align: middle;
|
|
1774
|
+
margin-left: 5px;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
/* ── advisor (M5): the unified alert/suggestion feed ───────────────────── */
|
|
1778
|
+
|
|
1779
|
+
.adv-item {
|
|
1780
|
+
display: grid;
|
|
1781
|
+
grid-template-columns: 96px minmax(0, 1fr) auto;
|
|
1782
|
+
gap: 8px 12px;
|
|
1783
|
+
align-items: start;
|
|
1784
|
+
padding: 10px 12px;
|
|
1785
|
+
border: 1px solid var(--rule);
|
|
1786
|
+
border-left-width: 3px;
|
|
1787
|
+
border-radius: var(--radius-sm);
|
|
1788
|
+
margin: 8px 0;
|
|
1789
|
+
background: var(--panel-2);
|
|
1790
|
+
}
|
|
1791
|
+
.adv-critical {
|
|
1792
|
+
border-left-color: var(--bad);
|
|
1793
|
+
}
|
|
1794
|
+
.adv-warn {
|
|
1795
|
+
border-left-color: var(--warn);
|
|
1796
|
+
}
|
|
1797
|
+
.adv-suggestion {
|
|
1798
|
+
border-left-color: var(--info);
|
|
1799
|
+
}
|
|
1800
|
+
.adv-main {
|
|
1801
|
+
min-width: 0;
|
|
1802
|
+
}
|
|
1803
|
+
.adv-title {
|
|
1804
|
+
font-weight: 550;
|
|
1805
|
+
}
|
|
1806
|
+
.adv-detail {
|
|
1807
|
+
margin-top: 2px;
|
|
1808
|
+
font-size: 12.5px;
|
|
1809
|
+
}
|
|
1810
|
+
.adv-guidance {
|
|
1811
|
+
margin-top: 4px;
|
|
1812
|
+
font-size: 12.5px;
|
|
1813
|
+
color: var(--ink-2);
|
|
1814
|
+
}
|
|
1815
|
+
.adv-actions {
|
|
1816
|
+
display: flex;
|
|
1817
|
+
flex-direction: column;
|
|
1818
|
+
align-items: flex-end;
|
|
1819
|
+
gap: 6px;
|
|
1820
|
+
}
|
|
1821
|
+
.adv-cli {
|
|
1822
|
+
font-size: 11px;
|
|
1823
|
+
color: var(--ink-3);
|
|
1824
|
+
font-family: var(--mono);
|
|
1825
|
+
max-width: 24rem;
|
|
1826
|
+
overflow: hidden;
|
|
1827
|
+
text-overflow: ellipsis;
|
|
1828
|
+
white-space: nowrap;
|
|
1829
|
+
}
|
|
1830
|
+
.adv-form {
|
|
1831
|
+
grid-column: 1 / -1;
|
|
1832
|
+
display: flex;
|
|
1833
|
+
gap: 8px;
|
|
1834
|
+
align-items: center;
|
|
1835
|
+
flex-wrap: wrap;
|
|
1836
|
+
}
|
|
1837
|
+
.adv-form .input {
|
|
1838
|
+
flex: 1 1 16rem;
|
|
1839
|
+
}
|
|
1840
|
+
.adv-optimal {
|
|
1841
|
+
border: 1px solid var(--ok);
|
|
1842
|
+
border-radius: var(--radius);
|
|
1843
|
+
background: var(--accent-ghost);
|
|
1844
|
+
padding: 16px 18px;
|
|
1845
|
+
margin: 10px 0;
|
|
1846
|
+
}
|
|
1847
|
+
.adv-note {
|
|
1848
|
+
font-size: 12.5px;
|
|
1849
|
+
color: var(--ink-2);
|
|
1850
|
+
}
|
|
1851
|
+
.adv-decision {
|
|
1852
|
+
font-size: 12px;
|
|
1853
|
+
color: var(--ink-3);
|
|
1854
|
+
}
|
|
1855
|
+
.adv-board-row {
|
|
1856
|
+
display: grid;
|
|
1857
|
+
grid-template-columns: minmax(10rem, 1fr) auto auto minmax(0, 2fr);
|
|
1858
|
+
gap: 8px 14px;
|
|
1859
|
+
align-items: center;
|
|
1860
|
+
padding: 9px 12px;
|
|
1861
|
+
border-bottom: 1px solid var(--rule);
|
|
1862
|
+
}
|
|
1863
|
+
.trend-bars {
|
|
1864
|
+
display: flex;
|
|
1865
|
+
align-items: flex-end;
|
|
1866
|
+
gap: 3px;
|
|
1867
|
+
height: 44px;
|
|
1868
|
+
margin: 6px 0;
|
|
1869
|
+
}
|
|
1870
|
+
.trend-bar {
|
|
1871
|
+
width: 14px;
|
|
1872
|
+
min-height: 2px;
|
|
1873
|
+
background: var(--accent-ghost);
|
|
1874
|
+
border-top: 2px solid var(--accent);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/* ── find harnesses (M5): discovery without registration ───────────────── */
|
|
1878
|
+
|
|
1879
|
+
.find-row {
|
|
1880
|
+
display: flex;
|
|
1881
|
+
gap: 10px;
|
|
1882
|
+
align-items: center;
|
|
1883
|
+
padding: 7px 10px;
|
|
1884
|
+
border-bottom: 1px solid var(--rule);
|
|
1885
|
+
}
|
|
1886
|
+
.find-row .mono {
|
|
1887
|
+
flex: 1 1 auto;
|
|
1888
|
+
min-width: 0;
|
|
1889
|
+
overflow: hidden;
|
|
1890
|
+
text-overflow: ellipsis;
|
|
1891
|
+
white-space: nowrap;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
@media (max-width: 720px) {
|
|
1895
|
+
.adv-item {
|
|
1896
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1897
|
+
}
|
|
1898
|
+
.adv-actions {
|
|
1899
|
+
align-items: flex-start;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
package/assets/js/api.js
CHANGED
|
@@ -262,7 +262,9 @@ export const api = {
|
|
|
262
262
|
groupProc: (name, verb, body = {}) => call(ROUTES.groupProc, { name, verb }, body),
|
|
263
263
|
setTags: (id, tags) => call(ROUTES.setTags, { id }, { tags }),
|
|
264
264
|
setPin: (id, pinned) => call(ROUTES.setPin, { id }, { pinned }),
|
|
265
|
+
setHidden: (id, hidden) => call(ROUTES.setHidden, { id }, { hidden }),
|
|
265
266
|
setNotes: (id, notes) => call(ROUTES.setNotes, { id }, { notes }),
|
|
267
|
+
discover: () => call(ROUTES.discover, {}),
|
|
266
268
|
relocateHarness: (id, newDir) => call(ROUTES.relocate, { id }, { newDir }),
|
|
267
269
|
removeHarness: (id) => call(ROUTES.removeHarness, { id }),
|
|
268
270
|
|
package/assets/js/app.js
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import { shapeAccent, shapeLabel } from "./shapes.js";
|
|
38
38
|
import { procRow } from "./supervision.js";
|
|
39
39
|
import { renderActivity } from "./views/activity.js";
|
|
40
|
+
import { renderAdvisor, renderAdvisorBoard } from "./views/advisor.js";
|
|
40
41
|
import { renderApprovals } from "./views/approvals.js";
|
|
41
42
|
import { renderChannels } from "./views/channels.js";
|
|
42
43
|
import { renderCosts } from "./views/costs.js";
|
|
@@ -68,6 +69,7 @@ import { renderTokenScreen } from "./views/token.js";
|
|
|
68
69
|
const THEME_KEY = "hangar.theme";
|
|
69
70
|
const TAB_LABELS = {
|
|
70
71
|
overview: "Overview",
|
|
72
|
+
advisor: "Advisor",
|
|
71
73
|
spec: "Spec",
|
|
72
74
|
runs: "Runs",
|
|
73
75
|
schedulers: "Schedulers",
|
|
@@ -94,6 +96,7 @@ const NAV = [
|
|
|
94
96
|
{ view: "review", label: "Review" },
|
|
95
97
|
{ view: "activity", label: "Activity" },
|
|
96
98
|
{ view: "health", label: "Health" },
|
|
99
|
+
{ view: "advisor", label: "Advisor" },
|
|
97
100
|
{ view: "credentials", label: "Credentials" },
|
|
98
101
|
{ view: "feedback", label: "Feedback" },
|
|
99
102
|
{ view: "thredz", label: "Thredz" },
|
|
@@ -352,6 +355,8 @@ async function dispatch(route) {
|
|
|
352
355
|
await renderThredzGlobal(viewRoot);
|
|
353
356
|
} else if (route.view === "health") {
|
|
354
357
|
await renderHealthBoard(viewRoot);
|
|
358
|
+
} else if (route.view === "advisor") {
|
|
359
|
+
await renderAdvisorBoard(viewRoot);
|
|
355
360
|
} else if (route.view === "settings") {
|
|
356
361
|
await renderSettings(viewRoot);
|
|
357
362
|
} else {
|
|
@@ -423,7 +428,8 @@ async function renderHarnessPage(root, route) {
|
|
|
423
428
|
if (route.tab === "spec") {
|
|
424
429
|
await renderSpec(tabRoot, ctx);
|
|
425
430
|
await renderSpecEdit(section(tabRoot), ctx);
|
|
426
|
-
} else if (route.tab === "
|
|
431
|
+
} else if (route.tab === "advisor") await renderAdvisor(tabRoot, ctx);
|
|
432
|
+
else if (route.tab === "runs") await renderRuns(tabRoot, ctx);
|
|
427
433
|
else if (route.tab === "schedulers") await renderSchedulers(tabRoot, ctx);
|
|
428
434
|
else if (route.tab === "sessions") await renderSessions(tabRoot, ctx);
|
|
429
435
|
else if (route.tab === "evals") {
|
package/assets/js/dom.js
CHANGED
|
@@ -161,6 +161,36 @@ export function copyBtn(value, label = "copy") {
|
|
|
161
161
|
return btn;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Attach a rich hover tooltip. The text rides a `data-tip` attribute the
|
|
166
|
+
* stylesheet renders via `attr()` — always text, never markup, so the
|
|
167
|
+
* injection ban holds — with the native `title` kept as the fallback for
|
|
168
|
+
* assistive tech and anything that never hovers. Returns the node.
|
|
169
|
+
*/
|
|
170
|
+
export function withTip(node, tipText) {
|
|
171
|
+
const value = String(tipText ?? "");
|
|
172
|
+
if (value === "") return node;
|
|
173
|
+
node.classList.add("has-tip");
|
|
174
|
+
node.dataset.tip = value;
|
|
175
|
+
if (!node.getAttribute("title")) node.setAttribute("title", value);
|
|
176
|
+
return node;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A small "?" affordance carrying only its tooltip — guidance on rows where
|
|
180
|
+
* a whole sentence inline would drown the content. Keyboard-focusable, so
|
|
181
|
+
* the tip is reachable without a pointer. */
|
|
182
|
+
export function tipIcon(tipText) {
|
|
183
|
+
return withTip(
|
|
184
|
+
el("span", {
|
|
185
|
+
class: "tip-icon",
|
|
186
|
+
tabindex: "0",
|
|
187
|
+
"aria-label": String(tipText ?? ""),
|
|
188
|
+
text: "?",
|
|
189
|
+
}),
|
|
190
|
+
tipText,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
164
194
|
/** A native collapsible (<details>) card. */
|
|
165
195
|
export function collapsible(summaryChildren, bodyChildren, open = false) {
|
|
166
196
|
return el("details", { class: "fold", open: open || null }, [
|
package/assets/js/router.js
CHANGED
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
|
|
41
41
|
export const HARNESS_TABS = [
|
|
42
42
|
"overview",
|
|
43
|
+
// M5: the unified alert/suggestion feed — right after Overview because it
|
|
44
|
+
// is the "what needs doing" screen; the strip order is the triage order.
|
|
45
|
+
"advisor",
|
|
43
46
|
"spec",
|
|
44
47
|
"runs",
|
|
45
48
|
"schedulers",
|
|
@@ -77,6 +80,9 @@ export const M3_TABS = [
|
|
|
77
80
|
"thredz",
|
|
78
81
|
"inspect",
|
|
79
82
|
"dev",
|
|
83
|
+
// M5: the advisor's sub-screens (a saved report, say) are the view's to
|
|
84
|
+
// name — the same generic `rest` capture the M3 tabs use.
|
|
85
|
+
"advisor",
|
|
80
86
|
];
|
|
81
87
|
|
|
82
88
|
/** The global (fleet-wide) screens, in nav order. `#/` is the Library. */
|
|
@@ -92,6 +98,9 @@ export const GLOBAL_VIEWS = [
|
|
|
92
98
|
// notification rules, read-only mode, the plugin inventory).
|
|
93
99
|
"health",
|
|
94
100
|
"settings",
|
|
101
|
+
// M5: the fleet advisor board — every harness's open alerts/suggestions,
|
|
102
|
+
// worst first.
|
|
103
|
+
"advisor",
|
|
95
104
|
];
|
|
96
105
|
|
|
97
106
|
/**
|
package/assets/js/routes.js
CHANGED
|
@@ -56,7 +56,15 @@ export const ROUTES = {
|
|
|
56
56
|
body: "GroupProc", // {force?, acknowledge?, parallel?}
|
|
57
57
|
},
|
|
58
58
|
addScanRoot: { method: "POST", path: "/api/registry/scan-roots", body: "ScanRootCreate" }, // {dir}
|
|
59
|
+
// Discovery WITHOUT registration: the harnesses under the scan roots that
|
|
60
|
+
// are NOT yet registered, each one an Add away — the "find harnesses on
|
|
61
|
+
// this machine" flow. Adding stays the default gesture; a blanket scan
|
|
62
|
+
// (which registers everything) is the bulk fallback, not the front door.
|
|
63
|
+
discover: { method: "GET", path: "/api/registry/discover" },
|
|
59
64
|
removeHarness: { method: "DELETE", path: "/api/h/:id" },
|
|
65
|
+
// Curation, not removal: hidden entries stay registered (state, groups,
|
|
66
|
+
// history intact) but the Library's default view omits them.
|
|
67
|
+
setHidden: { method: "PUT", path: "/api/h/:id/visibility", body: "SetVisibility" }, // {hidden}
|
|
60
68
|
relocate: { method: "POST", path: "/api/h/:id/relocate", body: "Relocate" }, // {newDir}
|
|
61
69
|
setGroups: { method: "PUT", path: "/api/h/:id/groups", body: "SetGroups" }, // {groups}
|
|
62
70
|
// One member's BOOT position inside a group; `order: null` clears it.
|
|
@@ -741,6 +749,60 @@ export const ROUTES = {
|
|
|
741
749
|
group: "inspect",
|
|
742
750
|
},
|
|
743
751
|
|
|
752
|
+
// ---- group "advisor": the unified alert/suggestion feed + its loops ----
|
|
753
|
+
// M5. Every alert, suggestion and optimization signal the manager can read
|
|
754
|
+
// about a harness, in ONE feed: each item carries its severity, the fact it
|
|
755
|
+
// came from, a tooltip-grade explanation, guidance, the screen that owns
|
|
756
|
+
// the fix, and a quick action (a queued CLI verb from a closed vocabulary,
|
|
757
|
+
// or a deep link — a suggestion is never an application). Acting takes an
|
|
758
|
+
// optional operator comment; dismissing REQUIRES a reason; both append to
|
|
759
|
+
// a harness-local ledger and reopen supersedes rather than deletes.
|
|
760
|
+
advisor: { method: "GET", path: "/api/h/:id/advisor", group: "advisor" },
|
|
761
|
+
advisorAct: {
|
|
762
|
+
method: "POST",
|
|
763
|
+
path: "/api/h/:id/advisor/:itemId/act",
|
|
764
|
+
body: "AdvisorAct", // {comment?}
|
|
765
|
+
group: "advisor",
|
|
766
|
+
},
|
|
767
|
+
advisorDismiss: {
|
|
768
|
+
method: "POST",
|
|
769
|
+
path: "/api/h/:id/advisor/:itemId/dismiss",
|
|
770
|
+
body: "AdvisorDismiss", // {reason}
|
|
771
|
+
group: "advisor",
|
|
772
|
+
},
|
|
773
|
+
advisorReopen: {
|
|
774
|
+
method: "POST",
|
|
775
|
+
path: "/api/h/:id/advisor/:itemId/reopen",
|
|
776
|
+
body: "Empty", // {}
|
|
777
|
+
group: "advisor",
|
|
778
|
+
},
|
|
779
|
+
// Improvement over time, folded from durable sources only (the eval index,
|
|
780
|
+
// the cost ledger, the decisions ledger) — a read never stores a snapshot.
|
|
781
|
+
advisorTrend: { method: "GET", path: "/api/h/:id/advisor/trend", group: "advisor" },
|
|
782
|
+
advisorReports: { method: "GET", path: "/api/h/:id/advisor/reports", group: "advisor" },
|
|
783
|
+
advisorReportRun: {
|
|
784
|
+
method: "POST",
|
|
785
|
+
path: "/api/h/:id/advisor/reports",
|
|
786
|
+
body: "AdvisorReportRun", // {kind: model-usage|costs|usefulness|optimization}
|
|
787
|
+
group: "advisor",
|
|
788
|
+
},
|
|
789
|
+
advisorReport: {
|
|
790
|
+
method: "GET",
|
|
791
|
+
path: "/api/h/:id/advisor/reports/:reportId",
|
|
792
|
+
group: "advisor",
|
|
793
|
+
},
|
|
794
|
+
// The issue inbox: a complaint submitted here is tuned into an update
|
|
795
|
+
// ready to run — `optimize` queues the eval→patch loop whose artifact is a
|
|
796
|
+
// reviewable spec patch; `note` records without queueing anything.
|
|
797
|
+
advisorIssues: { method: "GET", path: "/api/h/:id/advisor/issues", group: "advisor" },
|
|
798
|
+
advisorIssueSubmit: {
|
|
799
|
+
method: "POST",
|
|
800
|
+
path: "/api/h/:id/advisor/issues",
|
|
801
|
+
body: "AdvisorIssue", // {title, detail?, kind?}
|
|
802
|
+
group: "advisor",
|
|
803
|
+
},
|
|
804
|
+
advisorFleet: { method: "GET", path: "/api/advisor", group: "advisor" },
|
|
805
|
+
|
|
744
806
|
// ---- group "runtime": the mcp-server + dev run classes -----------------
|
|
745
807
|
// Both are PROCESSES: they get runfiles, ledger rows and ledger-claimed
|
|
746
808
|
// ports, and their live output rides the ONE existing SSE feed
|
|
@@ -763,8 +825,9 @@ export const ROUTES = {
|
|
|
763
825
|
devStop: { method: "POST", path: "/api/h/:id/dev/stop", body: "Empty", group: "runtime" },
|
|
764
826
|
};
|
|
765
827
|
|
|
766
|
-
/** The
|
|
767
|
-
* owns it on the server and the view
|
|
828
|
+
/** The grouped areas, in tab order (the M3 areas plus M5's advisor). A
|
|
829
|
+
* route's `group` names the module that owns it on the server and the view
|
|
830
|
+
* that renders it in the console. */
|
|
768
831
|
export const M3_GROUPS = [
|
|
769
832
|
"spec",
|
|
770
833
|
"memory",
|
|
@@ -777,6 +840,7 @@ export const M3_GROUPS = [
|
|
|
777
840
|
"thredz",
|
|
778
841
|
"inspect",
|
|
779
842
|
"runtime",
|
|
843
|
+
"advisor",
|
|
780
844
|
];
|
|
781
845
|
|
|
782
846
|
/** Every route key belonging to `group`, in map order. The views render
|
package/assets/js/util.js
CHANGED
|
@@ -180,6 +180,7 @@ export function normalizeRows(feed) {
|
|
|
180
180
|
groups: Array.isArray(src.groups) ? src.groups.map(String) : [],
|
|
181
181
|
tags: Array.isArray(src.tags) ? src.tags.map(String) : [],
|
|
182
182
|
pinned: src.pinned === true,
|
|
183
|
+
hidden: src.hidden === true,
|
|
183
184
|
notes: typeof src.notes === "string" ? src.notes : "",
|
|
184
185
|
missingSince: typeof src.missingSince === "string" ? src.missingSince : null,
|
|
185
186
|
lastSeen: typeof src.lastSeen === "string" ? src.lastSeen : null,
|