fix(desktop): usability wiring — rubric fixture ids + cell-jump listener + artifact seed + empty panel
+ docs/integration-final-check.md — 5-item integration gate PASS
This commit is contained in:
99
examples/desktop/docs/integration-final-check.md
Normal file
99
examples/desktop/docs/integration-final-check.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Integration final check — post-triple-merge
|
||||
|
||||
- **Mainline HEAD**: `08837bd96717fcc7f63e38d7f6918d35f13bec00`
|
||||
- `merge fix/usability-p0-batch @ 28e4f36 — usability P0 wiring: rubric fixture ids, cell-jump listener, artifact seed, empty panel`
|
||||
- Parent chain: usability 08837bd ← nav-optional de2f812 ← visual-fix + stability batch.
|
||||
- **Tests**: 1813/1813 pass (baseline 1789 pre-merge cold clone + 24 usability additions = 1813; static gates four-pack all green).
|
||||
- **Timestamp**: 2026-07-19
|
||||
- **Gate agent**: qa-gate-6, isolated Electron on port 9445 with `--user-data-dir=/tmp/dsh-integration-final-userdata` + `DSH_DESKTOP_HOME=/tmp/dsh-integration-final-home`. User's daily Electron instances untouched.
|
||||
|
||||
## 1. Full test — **PASS**
|
||||
|
||||
```
|
||||
$ LEFTHOOK=0 node --test test/*.test.js
|
||||
# tests 1813
|
||||
# pass 1813
|
||||
# fail 0
|
||||
```
|
||||
|
||||
## 2. Static gates four-pack — **PASS**
|
||||
|
||||
```
|
||||
$ LEFTHOOK=0 node --test test/style-css-static.test.js test/emoji-ban-static.test.js test/renderer-collisions.test.js
|
||||
# tests 12
|
||||
# pass 12
|
||||
# fail 0
|
||||
```
|
||||
|
||||
Gates covered:
|
||||
- **brace-balance** — `style-css-static.test.js::brace balance — style.css blocks close cleanly` PASS.
|
||||
- **centered-card ban** — `style-css-static.test.js::centered-card ban — no margin:auto / align-self:center on card family` PASS (density-spec §7).
|
||||
- **emoji-ban** — `emoji-ban-static.test.js` PASS (shipped code paths only).
|
||||
- **css-collision / renderer-collisions** — `renderer-collisions.test.js` PASS.
|
||||
|
||||
## 3. Fresh Electron integration smoke — **PASS**
|
||||
|
||||
Cold-boot on port 9445 with fresh userdata + isolated DSH_DESKTOP_HOME. Existing Electrons (ports 9227, 9271) left untouched.
|
||||
|
||||
### ① Console zero SyntaxError / zero uncaught
|
||||
```
|
||||
$ node /tmp/dsh-integration-smoke.mjs errors 4000
|
||||
{ "consoleErrors": [], "exceptions": [] }
|
||||
```
|
||||
|
||||
### ② 14+ pages page-by-page no whitescreen
|
||||
Setting `hiddenPages=[]` reveals all 14 nav items; each `switchTo` reaches a non-empty pane (min visibleTextLen=584 chars for playground-shim redirect stub, max 5583 for Missions). No whitescreen on any page. `playground-shim` is a redirect to Plugins (intentional; ships as a demo-tier chip until lane-playground-page lands), and Optional pages (Playground/Missions) correctly toggle visible when `hiddenPages=[]`.
|
||||
|
||||
### ③ Chat page three-layer views switchable
|
||||
- List/Graph tab switcher wired via `.chat-view-tab[data-chat-view-tab]`; clicking Graph flips `.pane[data-pane="chat"][data-chat-view]` to `graph` and mounts `#chat-session-graph` (SVG or empty-state text present).
|
||||
- Details drawer (`#chat-side-drawer-btn`, `.chat-side-drawer`) is visible (`display:flex`, `aria-expanded="true"`) on cold boot; toggling the button flips the drawer state.
|
||||
|
||||
### ④ Context page proportion bars render
|
||||
`.context-band` / `.ctx-share-bar` element count on Context tab: **61** (session default renders inject/compact/recall bands even with no active session, per the "Open a session to see..." empty state).
|
||||
|
||||
### ⑤ Rubrics 7 tiles have data + Runtime cell-jump to tracing
|
||||
- Rubrics page: **7 tiles** rendered, all 7 have data payloads (`rubricWithData: 7`). Matches the shipped `docs/rubric-fusion-fixture.json` count.
|
||||
- Cell-jump: clicking a `.rubric-grid-cell` on Rubrics page navigates to Tracing tab (`afterRubricClick: "tracing"`). Runtimes page presents 34 rubric-grid cells with the same wiring.
|
||||
|
||||
### ⑥ Artifacts panel first-visit + mock button triggers Board display
|
||||
- On Chat page first visit: `.artifact-panel` mounts eagerly via `ensureEmptyPanel()` before any artifact event.
|
||||
- Empty-state hint (`.artifact-panel-empty[data-role="empty-state"]`) is present until first event.
|
||||
- Clicking Hub → `#mock-artifact` button seeds fixtures via `seedBoardFixture(seed.artifacts)`: 6 list items + 12 artifact cards rendered, empty hint cleared.
|
||||
|
||||
### ⑦ Trace signal chip
|
||||
Verified by unit test — `test/trace-signal-detect.test.js` and `test/turn-signal-chip.test.js` cover chip attach path (`applyTurnSignalChips` in renderer.js:1576). In cold-boot mock-inject scenarios the loop/redundant thresholds do not fire (mocks emit single-turn events that don't cross detection thresholds); chips do render in real streaming sessions when detectSignals returns non-empty (behavior-tested in the green suite).
|
||||
|
||||
### ⑧ hiddenPages three-state validated
|
||||
| Config | Hidden buttons (data-tab) | Result |
|
||||
|---|---|---|
|
||||
| `hiddenPages: undefined` (default) | `["playground-shim", "mission"]` | PASS — DEFAULT_HIDDEN applied |
|
||||
| `hiddenPages: []` | `[]` | PASS — all nav items visible |
|
||||
| `hiddenPages: ["prs"]` | `["prs"]` | PASS — custom list honored |
|
||||
|
||||
IPC `nav:setHiddenPages` correctly rejects non-array input; `nav-config-model.resolveHiddenPages(cfg)` returns three distinct branches (missing → default set, empty array → nothing hidden, non-empty → honored as-is).
|
||||
|
||||
## 4. Cold-clone end-to-end — **PASS**
|
||||
|
||||
Fresh clone of dev-clone branch to `/tmp/dsh-final-clone` (PR head `d308769d2...`; usability merge rides on top of nav in the main-repo assembly, PR includes both when this doc pushes).
|
||||
|
||||
```
|
||||
$ git clone -b feat/dsh-desktop-shell ~/harness/deepseek-harness-dev /tmp/dsh-final-clone
|
||||
$ cd /tmp/dsh-final-clone && pnpm install --prefer-offline --ignore-scripts
|
||||
$ cd examples/desktop && npm install
|
||||
$ LEFTHOOK=0 npm test
|
||||
# tests 1789
|
||||
# pass 1789
|
||||
# fail 0
|
||||
$ DSH_DESKTOP_HOME=/tmp/dsh-final-clone-home node test/smoke-runtime.js stdio
|
||||
=== stdio-echo ===
|
||||
[stdio] status=starting
|
||||
[stdio] 6 notifications
|
||||
[stdio] status=dead
|
||||
[smoke] OK
|
||||
```
|
||||
|
||||
`stdio-echo` preflight sends one prompt over the JSON-RPC wire, receives the six-event notification stream (`turn/start`, `user/message`, `step/start`, `step/end`, `turn/end`, `session.finished`), and reports `[smoke] OK`. External cloner usability confirmed.
|
||||
|
||||
## 5. Conclusion — **整体可用**
|
||||
|
||||
All 5 gate items passed on final HEAD `08837bd96717fcc7f63e38d7f6918d35f13bec00`. No blocking issues surfaced by the integration probe. Zero console errors, zero uncaught exceptions during cold boot + 14-page sweep + hiddenPages three-state exercise. Cold-clone external usability verified via stdio-echo preflight.
|
||||
@@ -2,7 +2,7 @@
|
||||
"_note": "Rubric fusion fixture — feeds Rubrics/Growth/Runtime demo. All demo-tier data.",
|
||||
"rubrics": [
|
||||
{
|
||||
"id": "svg-gen",
|
||||
"id": "svg-generation",
|
||||
"name": "SVG generation",
|
||||
"group": "interaction-reasoning",
|
||||
"description": "Iterative SVG editing over 3-5 turns. Judged for shape correctness, style consistency, and no-regression.",
|
||||
@@ -33,6 +33,42 @@
|
||||
{ "id": "fix-effectiveness", "label": "Fix effectiveness", "type": "continuous", "min": 1, "max": 5 },
|
||||
{ "id": "convergence", "label": "Convergence", "type": "continuous", "min": 1, "max": 5 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "code-review",
|
||||
"name": "Code review",
|
||||
"group": "se-process",
|
||||
"description": "Reviewer rubric — style, correctness, test-coverage feedback quality.",
|
||||
"dims": [
|
||||
{ "id": "verdict", "label": "Review verdict", "type": "categorical", "values": ["reject", "revise", "approve"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "correctness-score",
|
||||
"name": "Correctness score",
|
||||
"group": "fix-optimize",
|
||||
"description": "Continuous 0-1 correctness on trajectory's stated goal.",
|
||||
"dims": [
|
||||
{ "id": "correctness", "label": "Correctness", "type": "continuous", "min": 0, "max": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "intent-triage",
|
||||
"name": "Intent triage",
|
||||
"group": "interaction-reasoning",
|
||||
"description": "Categorical verdict — bad · ok · good.",
|
||||
"dims": [
|
||||
{ "id": "verdict", "label": "Triage verdict", "type": "categorical", "values": ["bad", "ok", "good"] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "passes-bench",
|
||||
"name": "Passes bench",
|
||||
"group": "se-process",
|
||||
"description": "Boolean pass/fail on bench probe.",
|
||||
"dims": [
|
||||
{ "id": "passes", "label": "Passes bench probe", "type": "boolean", "labels": { "true": "pass", "false": "fail" } }
|
||||
]
|
||||
}
|
||||
],
|
||||
"similarClasses": [
|
||||
@@ -45,31 +81,31 @@
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{ "ts": 1720915200000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-1", "turnId": "t1", "rolloutIdx": 1, "score": 0.42, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1720915200000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-1", "turnId": "t1", "rolloutIdx": 1, "score": 0.55, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1720915200000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-1", "turnId": "t1", "rolloutIdx": 1, "score": true, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721001600000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-2", "turnId": "t2", "rolloutIdx": 2, "score": 0.51, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721001600000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-2", "turnId": "t2", "rolloutIdx": 2, "score": 0.60, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721001600000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-2", "turnId": "t2", "rolloutIdx": 2, "score": false, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721088000000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-3", "turnId": "t3", "rolloutIdx": 3, "score": 0.72, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721088000000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-3", "turnId": "t3", "rolloutIdx": 3, "score": 0.78, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721088000000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-3", "turnId": "t3", "rolloutIdx": 3, "score": true, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-4", "turnId": "t4", "rolloutIdx": 4, "score": 0.85, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-4", "turnId": "t4", "rolloutIdx": 4, "score": 0.82, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-4", "turnId": "t4", "rolloutIdx": 4, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-5", "turnId": "t5", "rolloutIdx": 5, "score": 0.91, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-5", "turnId": "t5", "rolloutIdx": 5, "score": 0.88, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-5", "turnId": "t5", "rolloutIdx": 5, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1720915200000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-1", "turnId": "t1", "rolloutIdx": 1, "score": 0.42, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1720915200000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-1", "turnId": "t1", "rolloutIdx": 1, "score": 0.55, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1720915200000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-1", "turnId": "t1", "rolloutIdx": 1, "score": true, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721001600000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-2", "turnId": "t2", "rolloutIdx": 2, "score": 0.51, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721001600000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-2", "turnId": "t2", "rolloutIdx": 2, "score": 0.60, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721001600000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-2", "turnId": "t2", "rolloutIdx": 2, "score": false, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1721088000000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-3", "turnId": "t3", "rolloutIdx": 3, "score": 0.72, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721088000000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-3", "turnId": "t3", "rolloutIdx": 3, "score": 0.78, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721088000000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-3", "turnId": "t3", "rolloutIdx": 3, "score": true, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-4", "turnId": "t4", "rolloutIdx": 4, "score": 0.85, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-4", "turnId": "t4", "rolloutIdx": 4, "score": 0.82, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-4", "turnId": "t4", "rolloutIdx": 4, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-5", "turnId": "t5", "rolloutIdx": 5, "score": 0.91, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-5", "turnId": "t5", "rolloutIdx": 5, "score": 0.88, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-5", "turnId": "t5", "rolloutIdx": 5, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 6, "score": 0.63, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 6, "score": 0.71, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 6, "score": false, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 7, "score": 0.80, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 7, "score": 0.83, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 7, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "shape-correct", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 8, "score": 0.88, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "style-consistent", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 8, "score": 0.85, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-gen", "dimId": "no-regress", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 8, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 6, "score": 0.63, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 6, "score": 0.71, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 6, "score": false, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 7, "score": 0.80, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 7, "score": 0.83, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 7, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "shape-correct", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 8, "score": 0.88, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "style-consistent", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 8, "score": 0.85, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "svg-generation", "dimId": "no-regress", "sessionId": "s-svg-live", "turnId": "tL", "rolloutIdx": 8, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
|
||||
{ "ts": 1720915200000, "rubricId": "bug-fix", "dimId": "reproduces", "sessionId": "s-bug-1", "turnId": "t1", "rolloutIdx": 1, "score": false, "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
{ "ts": 1720915200000, "rubricId": "bug-fix", "dimId": "minimality", "sessionId": "s-bug-1", "turnId": "t1", "rolloutIdx": 1, "score": "heavy", "harnessVersion": "v0.9", "model": "deepseek-r1", "dataMix": "mix-a" },
|
||||
@@ -89,6 +125,22 @@
|
||||
{ "ts": 1721174400000, "rubricId": "multi-turn-feedback", "dimId": "convergence", "sessionId": "s-mt-2", "turnId": "t2", "rolloutIdx": 1, "score": 4, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "multi-turn-feedback", "dimId": "feedback-understanding", "sessionId": "s-mt-3", "turnId": "t3", "rolloutIdx": 1, "score": 5, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "multi-turn-feedback", "dimId": "fix-effectiveness", "sessionId": "s-mt-3", "turnId": "t3", "rolloutIdx": 1, "score": 5, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
{ "ts": 1721260800000, "rubricId": "multi-turn-feedback", "dimId": "convergence", "sessionId": "s-mt-3", "turnId": "t3", "rolloutIdx": 1, "score": 5, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" }
|
||||
{ "ts": 1721260800000, "rubricId": "multi-turn-feedback", "dimId": "convergence", "sessionId": "s-mt-3", "turnId": "t3", "rolloutIdx": 1, "score": 5, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
|
||||
{ "ts": 1721088000000, "rubricId": "code-review", "dimId": "verdict", "sessionId": "s-cr-1", "turnId": "t1", "rolloutIdx": 1, "score": "revise", "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "code-review", "dimId": "verdict", "sessionId": "s-cr-2", "turnId": "t2", "rolloutIdx": 1, "score": "approve", "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "code-review", "dimId": "verdict", "sessionId": "s-cr-3", "turnId": "t3", "rolloutIdx": 1, "score": "approve", "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
|
||||
{ "ts": 1721088000000, "rubricId": "correctness-score", "dimId": "correctness", "sessionId": "s-cs-1", "turnId": "t1", "rolloutIdx": 1, "score": 0.62, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "correctness-score", "dimId": "correctness", "sessionId": "s-cs-2", "turnId": "t2", "rolloutIdx": 1, "score": 0.78, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "correctness-score", "dimId": "correctness", "sessionId": "s-cs-3", "turnId": "t3", "rolloutIdx": 1, "score": 0.85, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
|
||||
{ "ts": 1721088000000, "rubricId": "intent-triage", "dimId": "verdict", "sessionId": "s-it-1", "turnId": "t1", "rolloutIdx": 1, "score": "ok", "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "intent-triage", "dimId": "verdict", "sessionId": "s-it-2", "turnId": "t2", "rolloutIdx": 1, "score": "good", "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "intent-triage", "dimId": "verdict", "sessionId": "s-it-3", "turnId": "t3", "rolloutIdx": 1, "score": "good", "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" },
|
||||
|
||||
{ "ts": 1721088000000, "rubricId": "passes-bench", "dimId": "passes", "sessionId": "s-pb-1", "turnId": "t1", "rolloutIdx": 1, "score": false, "harnessVersion": "v0.10", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721174400000, "rubricId": "passes-bench", "dimId": "passes", "sessionId": "s-pb-2", "turnId": "t2", "rolloutIdx": 1, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-b" },
|
||||
{ "ts": 1721260800000, "rubricId": "passes-bench", "dimId": "passes", "sessionId": "s-pb-3", "turnId": "t3", "rolloutIdx": 1, "score": true, "harnessVersion": "v0.11", "model": "deepseek-r1", "dataMix": "mix-c" }
|
||||
]
|
||||
}
|
||||
|
||||
27
examples/desktop/scripts/regen-artifact-board-seed.js
Normal file
27
examples/desktop/scripts/regen-artifact-board-seed.js
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
// Regenerates src/renderer/artifact-board-seed.js from
|
||||
// docs/artifact-board-fixture.json. Renderer runs at file:// so we inline
|
||||
// the fixture instead of fetch().
|
||||
//
|
||||
// Usage: node scripts/regen-artifact-board-seed.js
|
||||
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const root = path.resolve(__dirname, '..')
|
||||
const src = path.join(root, 'docs', 'artifact-board-fixture.json')
|
||||
const dst = path.join(root, 'src', 'renderer', 'artifact-board-seed.js')
|
||||
|
||||
const j = JSON.parse(fs.readFileSync(src, 'utf8'))
|
||||
const body = "// Auto-inlined artifact-board fixture. Renderer runs at file:// so we\n" +
|
||||
"// inline docs/artifact-board-fixture.json here instead of relying on fetch().\n" +
|
||||
"// To refresh: node scripts/regen-artifact-board-seed.js\n" +
|
||||
"\n" +
|
||||
"'use strict'\n" +
|
||||
";(function () {\n" +
|
||||
" if (typeof window === 'undefined') return\n" +
|
||||
" window.__dshArtifactBoardSeed = " + JSON.stringify(j, null, 2) + ";\n" +
|
||||
"})()\n"
|
||||
fs.writeFileSync(dst, body)
|
||||
console.log('wrote', dst, '(' + body.length + ' bytes)')
|
||||
27
examples/desktop/scripts/regen-rubric-fusion-seed.js
Normal file
27
examples/desktop/scripts/regen-rubric-fusion-seed.js
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
// Regenerates src/renderer/rubric-fusion-seed.js from
|
||||
// docs/rubric-fusion-fixture.json. Renderer runs at file:// so we inline
|
||||
// the fixture instead of fetch().
|
||||
//
|
||||
// Usage: node scripts/regen-rubric-fusion-seed.js
|
||||
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const root = path.resolve(__dirname, '..')
|
||||
const src = path.join(root, 'docs', 'rubric-fusion-fixture.json')
|
||||
const dst = path.join(root, 'src', 'renderer', 'rubric-fusion-seed.js')
|
||||
|
||||
const j = JSON.parse(fs.readFileSync(src, 'utf8'))
|
||||
const body = "// Auto-inlined fusion fixture. Renderer runs at file:// so we inline\n" +
|
||||
"// docs/rubric-fusion-fixture.json here instead of relying on fetch().\n" +
|
||||
"// To refresh: node scripts/regen-rubric-fusion-seed.js\n" +
|
||||
"\n" +
|
||||
"'use strict'\n" +
|
||||
";(function () {\n" +
|
||||
" if (typeof window === 'undefined') return\n" +
|
||||
" window.__dshRubricFusionSeed = " + JSON.stringify(j, null, 2) + ";\n" +
|
||||
"})()\n"
|
||||
fs.writeFileSync(dst, body)
|
||||
console.log('wrote', dst, '(' + body.length + ' bytes)')
|
||||
93
examples/desktop/src/renderer/artifact-board-seed.js
Normal file
93
examples/desktop/src/renderer/artifact-board-seed.js
Normal file
@@ -0,0 +1,93 @@
|
||||
// Auto-inlined artifact-board fixture. Renderer runs at file:// so we
|
||||
// inline docs/artifact-board-fixture.json here instead of relying on fetch().
|
||||
// To refresh: node scripts/regen-artifact-board-seed.js
|
||||
|
||||
'use strict'
|
||||
;(function () {
|
||||
if (typeof window === 'undefined') return
|
||||
window.__dshArtifactBoardSeed = {
|
||||
"_note": "Fixture for lane-artifact-v2 (Artifacts evolution chain + Board view). Each entry is one artifact:event payload the renderer would receive. Multi-version entries share an artifactId; blob content is included so the evolution diff pane can render real per-hop line-diffs. The real ArtifactServer does not include content in its broadcast — that's the constraint the strip's fallback 'content not preserved' note is calibrated for.",
|
||||
"artifacts": [
|
||||
{
|
||||
"artifactId": "session.md",
|
||||
"kind": "md",
|
||||
"version": 1,
|
||||
"seenAt": 1747000000000,
|
||||
"path": "/w/.artifacts/session.md",
|
||||
"blob": "# Session log\n\nTurn 1: user asked to build a landing page.\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "session.md",
|
||||
"kind": "md",
|
||||
"version": 2,
|
||||
"seenAt": 1747000060000,
|
||||
"path": "/w/.artifacts/session.md",
|
||||
"blob": "# Session log\n\nTurn 1: user asked to build a landing page.\nTurn 2: agent scaffolded landing.html and hero.svg.\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "session.md",
|
||||
"kind": "md",
|
||||
"version": 3,
|
||||
"seenAt": 1747000180000,
|
||||
"path": "/w/.artifacts/session.md",
|
||||
"blob": "# Session log\n\nTurn 1: user asked to build a landing page.\nTurn 2: agent scaffolded landing.html and hero.svg.\nTurn 3: agent added responsive styles and OG tags.\nTurn 4: agent added README with build steps.\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "landing.html",
|
||||
"kind": "html",
|
||||
"version": 1,
|
||||
"seenAt": 1747000030000,
|
||||
"path": "/w/.artifacts/landing.html",
|
||||
"blob": "<!doctype html>\n<html>\n<head><title>Landing</title></head>\n<body>\n <h1>Hello</h1>\n</body>\n</html>\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "landing.html",
|
||||
"kind": "html",
|
||||
"version": 2,
|
||||
"seenAt": 1747000090000,
|
||||
"path": "/w/.artifacts/landing.html",
|
||||
"blob": "<!doctype html>\n<html>\n<head>\n <title>Landing</title>\n <meta name=\"description\" content=\"A demo landing page\">\n</head>\n<body>\n <h1>Hello</h1>\n <p>Welcome to our product.</p>\n</body>\n</html>\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "hero.svg",
|
||||
"kind": "svg",
|
||||
"version": 1,
|
||||
"seenAt": 1747000045000,
|
||||
"path": "/w/.artifacts/hero.svg",
|
||||
"blob": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 200 100\">\n <rect width=\"200\" height=\"100\" fill=\"#eef\"/>\n</svg>\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "config.json",
|
||||
"kind": "json",
|
||||
"version": 1,
|
||||
"seenAt": 1747000120000,
|
||||
"path": "/w/.artifacts/config.json",
|
||||
"blob": "{\n \"name\": \"landing\",\n \"version\": \"0.1.0\"\n}\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "config.json",
|
||||
"kind": "json",
|
||||
"version": 2,
|
||||
"seenAt": 1747000200000,
|
||||
"path": "/w/.artifacts/config.json",
|
||||
"blob": "{\n \"name\": \"landing\",\n \"version\": \"0.2.0\",\n \"port\": 3000\n}\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "build.sh",
|
||||
"kind": "sh",
|
||||
"version": 1,
|
||||
"seenAt": 1747000150000,
|
||||
"path": "/w/.artifacts/build.sh",
|
||||
"blob": "#!/bin/sh\nset -e\nnpm install\nnpm run build\n"
|
||||
},
|
||||
{
|
||||
"artifactId": "README.md",
|
||||
"kind": "md",
|
||||
"version": 1,
|
||||
"seenAt": 1747000210000,
|
||||
"path": "/w/.artifacts/README.md",
|
||||
"blob": "# Landing\n\nStatic landing page for the demo product.\n\n## Build\n\n```sh\nsh build.sh\n```\n"
|
||||
}
|
||||
]
|
||||
};
|
||||
})()
|
||||
@@ -292,6 +292,20 @@
|
||||
head.append(label, summary)
|
||||
el.append(head)
|
||||
|
||||
// Provenance banner: the real ArtifactServer broadcasts fs writes
|
||||
// with no blob content, so per-hop diffs collapse to the "content
|
||||
// not preserved" fallback in production. When any hop is missing
|
||||
// blob content we surface why up front rather than letting the
|
||||
// researcher assume the diff pane is broken. Fixture supplies
|
||||
// blobs so all hops render; production doesn't, yet.
|
||||
const missingBlobs = chain.some((c) => typeof c.blob !== 'string')
|
||||
if (missingBlobs) {
|
||||
const banner = document.createElement('div')
|
||||
banner.className = 'artifact-evolution-banner muted small'
|
||||
banner.textContent = 'Version diff currently requires blob payload. The real ArtifactServer does not preserve older blobs (RFC L-3 follow-up: snapshot store). The fixture supplies blobs for demo.'
|
||||
el.append(banner)
|
||||
}
|
||||
|
||||
const chainEl = document.createElement('ol')
|
||||
chainEl.className = 'artifact-evolution-chain'
|
||||
for (let i = 0; i < chain.length; i++) {
|
||||
|
||||
@@ -176,6 +176,12 @@
|
||||
b.panelEl = buildPanel()
|
||||
stream.appendChild(b.panelEl)
|
||||
}
|
||||
// Remove the cold-clone empty-state hint (if present) once we have
|
||||
// a real artifact to show. Introduced by lane-usability-fix so
|
||||
// seedBoardFixture's placeholder gets swept when a real artifact
|
||||
// event arrives.
|
||||
const emptyHint = b.panelEl.querySelector('[data-role="empty-state"]')
|
||||
if (emptyHint && emptyHint.parentNode) emptyHint.parentNode.removeChild(emptyHint)
|
||||
const groupHost = b.panelEl.querySelector('.artifact-group')
|
||||
groupHost.appendChild(el)
|
||||
}
|
||||
@@ -458,32 +464,87 @@
|
||||
setTimeout(() => el.classList.remove('artifact-flash'), 900)
|
||||
}
|
||||
|
||||
// Debug menu button — mocks a write into the artifact dir via IPC.
|
||||
// Debug menu button — seeds the board/timeline/evolution demo. The
|
||||
// real ArtifactServer broadcasts fs writes with no blob content, so
|
||||
// cold-clone researchers never see the Board/Timeline/Evolution
|
||||
// views' actual affordances. We route the multi-kind, multi-version
|
||||
// fixture through the same onArtifactEvent entry the real wire uses
|
||||
// so all three tabs light up via the production code path. When the
|
||||
// seed is unavailable we fall back to the original IPC single-file
|
||||
// mock.
|
||||
function bindMockButton() {
|
||||
const btn = document.getElementById('mock-artifact')
|
||||
if (!btn) return
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true
|
||||
try {
|
||||
await window.dsh.mockArtifact()
|
||||
const seed = typeof window !== 'undefined' ? window.__dshArtifactBoardSeed : null
|
||||
if (seed && Array.isArray(seed.artifacts) && seed.artifacts.length) {
|
||||
seedBoardFixture(seed.artifacts)
|
||||
} else if (window.dsh && typeof window.dsh.mockArtifact === 'function') {
|
||||
await window.dsh.mockArtifact()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('mockArtifact failed', err)
|
||||
console.error('mockArtifact / seed failed', err)
|
||||
} finally {
|
||||
btn.disabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Feed the board fixture through the same onArtifactEvent path the
|
||||
// real ArtifactServer uses. `seenAt` is re-anchored to "moments ago"
|
||||
// so relative-time labels don't read as ~430d against the frozen
|
||||
// fixture timestamps. Idempotent: ensureCard() de-dups on artifactId,
|
||||
// recordHistory() de-dups on (artifactId, version).
|
||||
function seedBoardFixture(list) {
|
||||
if (!Array.isArray(list) || !list.length) return
|
||||
const base = Date.now() - list.length * 60_000
|
||||
list.forEach((raw, i) => {
|
||||
onArtifactEvent({ ...raw, seenAt: base + i * 60_000 })
|
||||
})
|
||||
}
|
||||
|
||||
// Cold-clone empty state: mount the panel scaffold on stream init so
|
||||
// researchers can see List/Board/Timeline tabs (and the "no artifacts
|
||||
// yet" hint) exist even before the first artifact:event lands. Until
|
||||
// now the panel only appeared after the first artifact — new users
|
||||
// saw a blank pane and had no idea the surface existed. Scoped to the
|
||||
// active session bucket so switching sessions before any artifact
|
||||
// fires still gets its own empty panel per session.
|
||||
function ensureEmptyPanel() {
|
||||
const s = streamEl()
|
||||
if (!s) return
|
||||
const b = bucket()
|
||||
if (b.panelEl && b.panelEl.isConnected) return
|
||||
b.panelEl = buildPanel()
|
||||
// Inject a muted empty-state hint into the List body. Removed on
|
||||
// first appendGrouped() call so real artifacts land in a clean host.
|
||||
const body = b.panelEl.querySelector('.artifact-panel-body')
|
||||
if (body) {
|
||||
const hint = document.createElement('div')
|
||||
hint.className = 'artifact-panel-empty muted small'
|
||||
hint.dataset.role = 'empty-state'
|
||||
hint.textContent = 'No artifacts yet — try “artifact” in the Debug menu, or run a session that writes to .artifacts/.'
|
||||
body.appendChild(hint)
|
||||
}
|
||||
s.appendChild(b.panelEl)
|
||||
}
|
||||
|
||||
// Wire up once the DOM + preload bridge are ready. The renderer script
|
||||
// tag is loaded after this one, so we just register the listener
|
||||
// eagerly.
|
||||
if (window.dsh && typeof window.dsh.onArtifact === 'function') {
|
||||
window.dsh.onArtifact(onArtifactEvent)
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', bindMockButton)
|
||||
} else {
|
||||
function initDom() {
|
||||
bindMockButton()
|
||||
ensureEmptyPanel()
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initDom)
|
||||
} else {
|
||||
initDom()
|
||||
}
|
||||
|
||||
// Route artifact events on a session id if the payload carries one.
|
||||
@@ -510,6 +571,8 @@
|
||||
cards,
|
||||
history,
|
||||
switchView,
|
||||
seedBoardFixture,
|
||||
ensureEmptyPanel,
|
||||
getView: () => bucket().currentView,
|
||||
setActiveSession,
|
||||
getActiveSessionId: () => activeSessionId,
|
||||
|
||||
@@ -169,9 +169,20 @@ function renderSessionGraph(container, snapshot) {
|
||||
label.setAttribute('y', String(pos.y + 4))
|
||||
label.textContent = node.label
|
||||
g.appendChild(label)
|
||||
if (typeof snapshot?.onSelect === 'function' && node.turnId) {
|
||||
// Bind click on any node that carries an actionable identifier:
|
||||
// turn nodes → turnId, fork nodes → childSessionId, user nodes →
|
||||
// seq. Only these get the pointer cursor; nodes without a target
|
||||
// fall back to the default cursor so the affordance matches
|
||||
// reality.
|
||||
const actionable =
|
||||
(node.turnId) ||
|
||||
(node.kind === 'fork' && node.childSessionId) ||
|
||||
(node.kind === 'user' && node.seq)
|
||||
if (typeof snapshot?.onSelect === 'function' && actionable) {
|
||||
g.addEventListener('click', () => snapshot.onSelect(node))
|
||||
g.style && (g.style.cursor = 'pointer')
|
||||
} else if (g.style) {
|
||||
g.style.cursor = 'default'
|
||||
}
|
||||
svg.appendChild(g)
|
||||
}
|
||||
|
||||
@@ -1369,6 +1369,11 @@
|
||||
Evolution renderers) and MUST load before artifacts.js so the L0 row
|
||||
version-chip click handler + view switcher find the module ready. -->
|
||||
<script src="./artifacts-board.js"></script>
|
||||
<!-- Board fixture (multi-version + multi-kind demo data). Consumed by
|
||||
the debug-menu "seed artifacts" seam so external researchers can
|
||||
reach the Board/Timeline/Evolution views without waiting for a
|
||||
real run to emit fs artifacts. -->
|
||||
<script src="./artifact-board-seed.js"></script>
|
||||
<script src="./artifacts.js"></script>
|
||||
<script src="./plugin-runtime-fold.js"></script>
|
||||
<script src="./mcp-tool-name.js"></script>
|
||||
|
||||
@@ -4291,13 +4291,29 @@ function refreshChatSideDrawer() {
|
||||
sessionId: state.activeSessionId || '',
|
||||
model: meta && (meta.model || (meta.header && meta.header.model)) || '',
|
||||
events,
|
||||
selectedTurnId: null,
|
||||
selectedTurnId: state.chatSelectedTurnId || null,
|
||||
onSelect(row) {
|
||||
if (!row || !row.turnId) return
|
||||
if (!row) return
|
||||
// User rows have no turnId — scroll to the matching data-seq
|
||||
// anchor instead of the no-op early return. Cursor:pointer on
|
||||
// the whole row promises interactivity; without this the row
|
||||
// reads as a dead affordance.
|
||||
if (row.kind === 'user' && row.seq) {
|
||||
const seqTarget = streamEl && streamEl.querySelector(`[data-seq="${row.seq}"]`)
|
||||
if (seqTarget && typeof seqTarget.scrollIntoView === 'function') {
|
||||
seqTarget.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!row.turnId) return
|
||||
state.chatSelectedTurnId = row.turnId
|
||||
const target = streamEl && streamEl.querySelector(`[data-turn-id="${row.turnId}"]`)
|
||||
if (target && typeof target.scrollIntoView === 'function') {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
// Re-render so the .active highlight lands on the row we just
|
||||
// clicked. Keeps drawer state in sync with the stream focus.
|
||||
refreshChatSideDrawerIfOpen()
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -4343,15 +4359,34 @@ function refreshSessionGraph() {
|
||||
const events = (meta && Array.isArray(meta.cachedEvents)) ? meta.cachedEvents : []
|
||||
api.renderSessionGraph(chatSessionGraphEl, {
|
||||
sessionId: state.activeSessionId || '',
|
||||
selectedTurnId: state.chatSelectedTurnId || null,
|
||||
events,
|
||||
onSelect(node) {
|
||||
if (!node || !node.turnId) return
|
||||
if (!node) return
|
||||
// Fork nodes point at a child session — clicking follows the
|
||||
// fork. Without this the cursor:pointer node reads as dead.
|
||||
if (node.kind === 'fork' && node.childSessionId) {
|
||||
try { selectSession(node.childSessionId) } catch (_) { /* stale id */ }
|
||||
return
|
||||
}
|
||||
// User nodes have no turnId — scroll to the message via data-seq.
|
||||
if (node.kind === 'user' && node.seq) {
|
||||
setChatView('list')
|
||||
const sTarget = streamEl && streamEl.querySelector(`[data-seq="${node.seq}"]`)
|
||||
if (sTarget && typeof sTarget.scrollIntoView === 'function') {
|
||||
sTarget.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!node.turnId) return
|
||||
state.chatSelectedTurnId = node.turnId
|
||||
// Jump to the turn in the List view and focus it.
|
||||
setChatView('list')
|
||||
const target = streamEl && streamEl.querySelector(`[data-turn-id="${node.turnId}"]`)
|
||||
if (target && typeof target.scrollIntoView === 'function') {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
refreshChatSideDrawerIfOpen()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,13 +178,16 @@ function createStore() {
|
||||
// so without dedupe here, two-page seeding would double-insert every
|
||||
// event. Two-tier guard:
|
||||
// 1. WeakSet on the fixture object ref (fast path for the common
|
||||
// case where all pages read window.__dshRubricFusionSeed).
|
||||
// case where all pages read window.__dshRubricFusionSeed). Return
|
||||
// shape includes `deduped: true` on this fast-path hit so callers
|
||||
// can distinguish "already seeded" from "empty fixture".
|
||||
// 2. Per-event key dedupe inside addEvent() catches the case where a
|
||||
// caller passes a fresh literal that happens to carry the same
|
||||
// rows.
|
||||
// caller passes a fresh literal (or a deep-cloned copy) that
|
||||
// carries the same rows — the WeakSet won't help there because
|
||||
// the object identity differs.
|
||||
function loadFixture(json) {
|
||||
if (!json || typeof json !== 'object') return { rubrics: 0, events: 0 }
|
||||
if (state.loadedFixtures.has(json)) return { rubrics: 0, events: 0 }
|
||||
if (state.loadedFixtures.has(json)) return { rubrics: 0, events: 0, deduped: true }
|
||||
state.loadedFixtures.add(json)
|
||||
let rn = 0, en = 0
|
||||
for (const r of json.rubrics || []) { if (registerRubric(r)) rn++ }
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Auto-inlined fusion fixture. Renderer runs at file:// so we inline
|
||||
// docs/rubric-fusion-fixture.json here instead of relying on fetch().
|
||||
// To refresh: node scripts/regen-rubric-fusion-seed.js
|
||||
|
||||
'use strict'
|
||||
;(function () {
|
||||
if (typeof window === 'undefined') return
|
||||
@@ -5,7 +9,7 @@
|
||||
"_note": "Rubric fusion fixture — feeds Rubrics/Growth/Runtime demo. All demo-tier data.",
|
||||
"rubrics": [
|
||||
{
|
||||
"id": "svg-gen",
|
||||
"id": "svg-generation",
|
||||
"name": "SVG generation",
|
||||
"group": "interaction-reasoning",
|
||||
"description": "Iterative SVG editing over 3-5 turns. Judged for shape correctness, style consistency, and no-regression.",
|
||||
@@ -97,6 +101,74 @@
|
||||
"max": 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "code-review",
|
||||
"name": "Code review",
|
||||
"group": "se-process",
|
||||
"description": "Reviewer rubric — style, correctness, test-coverage feedback quality.",
|
||||
"dims": [
|
||||
{
|
||||
"id": "verdict",
|
||||
"label": "Review verdict",
|
||||
"type": "categorical",
|
||||
"values": [
|
||||
"reject",
|
||||
"revise",
|
||||
"approve"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "correctness-score",
|
||||
"name": "Correctness score",
|
||||
"group": "fix-optimize",
|
||||
"description": "Continuous 0-1 correctness on trajectory's stated goal.",
|
||||
"dims": [
|
||||
{
|
||||
"id": "correctness",
|
||||
"label": "Correctness",
|
||||
"type": "continuous",
|
||||
"min": 0,
|
||||
"max": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "intent-triage",
|
||||
"name": "Intent triage",
|
||||
"group": "interaction-reasoning",
|
||||
"description": "Categorical verdict — bad · ok · good.",
|
||||
"dims": [
|
||||
{
|
||||
"id": "verdict",
|
||||
"label": "Triage verdict",
|
||||
"type": "categorical",
|
||||
"values": [
|
||||
"bad",
|
||||
"ok",
|
||||
"good"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "passes-bench",
|
||||
"name": "Passes bench",
|
||||
"group": "se-process",
|
||||
"description": "Boolean pass/fail on bench probe.",
|
||||
"dims": [
|
||||
{
|
||||
"id": "passes",
|
||||
"label": "Passes bench probe",
|
||||
"type": "boolean",
|
||||
"labels": {
|
||||
"true": "pass",
|
||||
"false": "fail"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"similarClasses": [
|
||||
@@ -117,7 +189,7 @@
|
||||
"events": [
|
||||
{
|
||||
"ts": 1720915200000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-1",
|
||||
"turnId": "t1",
|
||||
@@ -129,7 +201,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1720915200000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-1",
|
||||
"turnId": "t1",
|
||||
@@ -141,7 +213,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1720915200000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-1",
|
||||
"turnId": "t1",
|
||||
@@ -153,7 +225,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721001600000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-2",
|
||||
"turnId": "t2",
|
||||
@@ -165,7 +237,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721001600000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-2",
|
||||
"turnId": "t2",
|
||||
@@ -177,7 +249,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721001600000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-2",
|
||||
"turnId": "t2",
|
||||
@@ -189,7 +261,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-3",
|
||||
"turnId": "t3",
|
||||
@@ -201,7 +273,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-3",
|
||||
"turnId": "t3",
|
||||
@@ -213,7 +285,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-3",
|
||||
"turnId": "t3",
|
||||
@@ -225,7 +297,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-4",
|
||||
"turnId": "t4",
|
||||
@@ -237,7 +309,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-4",
|
||||
"turnId": "t4",
|
||||
@@ -249,7 +321,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-4",
|
||||
"turnId": "t4",
|
||||
@@ -261,7 +333,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-5",
|
||||
"turnId": "t5",
|
||||
@@ -273,7 +345,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-5",
|
||||
"turnId": "t5",
|
||||
@@ -285,7 +357,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-5",
|
||||
"turnId": "t5",
|
||||
@@ -297,7 +369,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -309,7 +381,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -321,7 +393,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -333,7 +405,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -345,7 +417,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -357,7 +429,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -369,7 +441,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "shape-correct",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -381,7 +453,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "style-consistent",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -393,7 +465,7 @@
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "svg-gen",
|
||||
"rubricId": "svg-generation",
|
||||
"dimId": "no-regress",
|
||||
"sessionId": "s-svg-live",
|
||||
"turnId": "tL",
|
||||
@@ -618,6 +690,150 @@
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-c"
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "code-review",
|
||||
"dimId": "verdict",
|
||||
"sessionId": "s-cr-1",
|
||||
"turnId": "t1",
|
||||
"rolloutIdx": 1,
|
||||
"score": "revise",
|
||||
"harnessVersion": "v0.10",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "code-review",
|
||||
"dimId": "verdict",
|
||||
"sessionId": "s-cr-2",
|
||||
"turnId": "t2",
|
||||
"rolloutIdx": 1,
|
||||
"score": "approve",
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "code-review",
|
||||
"dimId": "verdict",
|
||||
"sessionId": "s-cr-3",
|
||||
"turnId": "t3",
|
||||
"rolloutIdx": 1,
|
||||
"score": "approve",
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-c"
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "correctness-score",
|
||||
"dimId": "correctness",
|
||||
"sessionId": "s-cs-1",
|
||||
"turnId": "t1",
|
||||
"rolloutIdx": 1,
|
||||
"score": 0.62,
|
||||
"harnessVersion": "v0.10",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "correctness-score",
|
||||
"dimId": "correctness",
|
||||
"sessionId": "s-cs-2",
|
||||
"turnId": "t2",
|
||||
"rolloutIdx": 1,
|
||||
"score": 0.78,
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "correctness-score",
|
||||
"dimId": "correctness",
|
||||
"sessionId": "s-cs-3",
|
||||
"turnId": "t3",
|
||||
"rolloutIdx": 1,
|
||||
"score": 0.85,
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-c"
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "intent-triage",
|
||||
"dimId": "verdict",
|
||||
"sessionId": "s-it-1",
|
||||
"turnId": "t1",
|
||||
"rolloutIdx": 1,
|
||||
"score": "ok",
|
||||
"harnessVersion": "v0.10",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "intent-triage",
|
||||
"dimId": "verdict",
|
||||
"sessionId": "s-it-2",
|
||||
"turnId": "t2",
|
||||
"rolloutIdx": 1,
|
||||
"score": "good",
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "intent-triage",
|
||||
"dimId": "verdict",
|
||||
"sessionId": "s-it-3",
|
||||
"turnId": "t3",
|
||||
"rolloutIdx": 1,
|
||||
"score": "good",
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-c"
|
||||
},
|
||||
{
|
||||
"ts": 1721088000000,
|
||||
"rubricId": "passes-bench",
|
||||
"dimId": "passes",
|
||||
"sessionId": "s-pb-1",
|
||||
"turnId": "t1",
|
||||
"rolloutIdx": 1,
|
||||
"score": false,
|
||||
"harnessVersion": "v0.10",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721174400000,
|
||||
"rubricId": "passes-bench",
|
||||
"dimId": "passes",
|
||||
"sessionId": "s-pb-2",
|
||||
"turnId": "t2",
|
||||
"rolloutIdx": 1,
|
||||
"score": true,
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-b"
|
||||
},
|
||||
{
|
||||
"ts": 1721260800000,
|
||||
"rubricId": "passes-bench",
|
||||
"dimId": "passes",
|
||||
"sessionId": "s-pb-3",
|
||||
"turnId": "t3",
|
||||
"rolloutIdx": 1,
|
||||
"score": true,
|
||||
"harnessVersion": "v0.11",
|
||||
"model": "deepseek-r1",
|
||||
"dataMix": "mix-c"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -12574,6 +12574,18 @@ button.artifact-version:hover {
|
||||
border: 1px dashed var(--border); border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.artifact-evolution-banner {
|
||||
padding: 6px 8px; font-size: 11px; line-height: 1.4;
|
||||
border-left: 2px solid var(--border);
|
||||
background: var(--bg-elev);
|
||||
border-radius: 0 4px 4px 0;
|
||||
margin: 4px 0 6px 0;
|
||||
}
|
||||
.artifact-panel-empty {
|
||||
padding: 12px 10px;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.artifact-evolution-diff-body {
|
||||
margin-top: 4px;
|
||||
border: 1px solid var(--border); border-radius: 4px;
|
||||
|
||||
@@ -472,4 +472,17 @@
|
||||
// tri-view instead of on the index. Auto-drill only fires when the caller
|
||||
// has a valid sessionId; the internal path already handles refresh + rehydrate.
|
||||
window.__dshTracingPage = { show, closeDrill, openDrill }
|
||||
|
||||
// Runtime → Rubric grid cell click dispatches `dsh:rubric-cell-jump`. The
|
||||
// upstream fires with { sessionId, turnId } detail and calls __dshTabs to
|
||||
// switch, but drops the ids. Wire the drill here so clicking a cell
|
||||
// actually lands the user inside that session's trace — the whole value
|
||||
// prop of the grid.
|
||||
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
||||
window.addEventListener('dsh:rubric-cell-jump', function (ev) {
|
||||
const detail = ev && ev.detail
|
||||
if (!detail || !detail.sessionId) return
|
||||
try { openDrill(detail.sessionId) } catch (_) { /* drill unavailable — swallow */ }
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -29,10 +29,14 @@ function freshFusion() {
|
||||
const FIXTURE_PATH = path.join(__dirname, '..', 'docs', 'rubric-fusion-fixture.json')
|
||||
const FIXTURE = JSON.parse(fs.readFileSync(FIXTURE_PATH, 'utf8'))
|
||||
|
||||
test('fixture loads with 3 rubrics and > 30 events', () => {
|
||||
test('fixture loads with catalog-aligned rubrics and > 30 events', () => {
|
||||
const s = freshFusion()
|
||||
const res = s.loadFixture(FIXTURE)
|
||||
assert.equal(res.rubrics, 3)
|
||||
// Fixture now covers all 7 catalog rubrics (svg-generation, bug-fix,
|
||||
// multi-turn-feedback, code-review, correctness-score, intent-triage,
|
||||
// passes-bench) so every Rubrics tile has stats. Before the fix only
|
||||
// 3 rubrics were seeded and 4 tiles read as "No scores yet."
|
||||
assert.equal(res.rubrics, 7)
|
||||
assert.ok(res.events > 30, 'expected > 30 events, got ' + res.events)
|
||||
})
|
||||
|
||||
@@ -92,8 +96,8 @@ test('Growth view: filter chip harnessVersion narrows the series', () => {
|
||||
test('Runtime view: rolloutGridFor produces a matrix with cell pass/fail', () => {
|
||||
const s = freshFusion()
|
||||
s.loadFixture(FIXTURE)
|
||||
const grid = s.rolloutGridFor('svg-gen', 's-svg-live')
|
||||
assert.equal(grid.rubric.id, 'svg-gen')
|
||||
const grid = s.rolloutGridFor('svg-generation', 's-svg-live')
|
||||
assert.equal(grid.rubric.id, 'svg-generation')
|
||||
assert.ok(grid.rollouts.length >= 3, 'expected >= 3 rollouts for the live session')
|
||||
assert.ok(grid.dims.length === 3, 'svg-gen has 3 dims')
|
||||
// Assert we have at least one pass AND at least one fail — the fixture
|
||||
@@ -107,7 +111,7 @@ test('Runtime view: rolloutGridFor produces a matrix with cell pass/fail', () =>
|
||||
test('Runtime view: rolloutGridFor sessionId=null aggregates all rollouts', () => {
|
||||
const s = freshFusion()
|
||||
s.loadFixture(FIXTURE)
|
||||
const grid = s.rolloutGridFor('svg-gen', null)
|
||||
const grid = s.rolloutGridFor('svg-generation', null)
|
||||
// The fixture has rollouts 1..8 for svg-gen (5 seed sessions + 3 on
|
||||
// the live session).
|
||||
assert.deepEqual(grid.rollouts, [1, 2, 3, 4, 5, 6, 7, 8])
|
||||
|
||||
100
examples/desktop/test/usability-p0-batch.test.js
Normal file
100
examples/desktop/test/usability-p0-batch.test.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// Test locks for the fix/usability-p0-batch cycle. Each locks one of the
|
||||
// P0 findings from review-usability: fixture id/catalog match, fusion
|
||||
// loadFixture idempotency, rubric-cell-jump listener presence, artifact
|
||||
// blob banner on missing-blob chains, artifact seed API surface.
|
||||
|
||||
'use strict'
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const RENDERER = path.resolve(__dirname, '..', 'src', 'renderer')
|
||||
|
||||
// -- P0-3 fusion loadFixture is idempotent ---------------------------------
|
||||
test('rubric-fusion loadFixture is idempotent by fixture identity', () => {
|
||||
const fusion = require(path.join(RENDERER, 'rubric-fusion-model.js'))
|
||||
const store = fusion.create()
|
||||
const fixture = {
|
||||
rubrics: [{ id: 'r1', name: 'R1', dims: [{ id: 'd1', label: 'D1', type: 'continuous', min: 0, max: 1 }] }],
|
||||
events: [{ ts: 1, rubricId: 'r1', dimId: 'd1', sessionId: 's', turnId: 't', score: 0.5 }],
|
||||
}
|
||||
const first = store.loadFixture(fixture)
|
||||
const second = store.loadFixture(fixture)
|
||||
const third = store.loadFixture(fixture)
|
||||
assert.strictEqual(first.events, 1, 'first load registers events')
|
||||
assert.strictEqual(second.events, 0, 'second load is a no-op')
|
||||
assert.ok(second.deduped, 'second load reports deduped flag')
|
||||
assert.strictEqual(third.events, 0, 'third load is a no-op')
|
||||
assert.strictEqual(store.listEvents({}).length, 1, 'event count stays at 1 after 3 loads')
|
||||
})
|
||||
|
||||
// -- P0-1 fixture rubric ids match catalog ids -----------------------------
|
||||
test('rubric-fusion fixture covers every catalog rubric id', () => {
|
||||
const seedSrc = fs.readFileSync(path.join(RENDERER, 'rubrics-seed.js'), 'utf8')
|
||||
// Parse rubric names out of the inlined SKILL.md blobs.
|
||||
const catalogIds = []
|
||||
for (const m of seedSrc.matchAll(/---\\nname: ([\w-]+)/g)) catalogIds.push(m[1])
|
||||
assert.ok(catalogIds.length >= 4, 'catalog seed has at least 4 rubrics')
|
||||
const fixtureJson = JSON.parse(fs.readFileSync(
|
||||
path.join(__dirname, '..', 'docs', 'rubric-fusion-fixture.json'), 'utf8'))
|
||||
const fixtureIds = new Set((fixtureJson.rubrics || []).map(r => r.id))
|
||||
const missing = catalogIds.filter(id => !fixtureIds.has(id))
|
||||
assert.deepStrictEqual(missing, [],
|
||||
'every catalog rubric id has a matching fixture rubric def: missing=' + missing.join(','))
|
||||
})
|
||||
|
||||
// -- P0-2 tracing-page registers rubric-cell-jump listener -----------------
|
||||
test('tracing-page wires dsh:rubric-cell-jump → openDrill', () => {
|
||||
const src = fs.readFileSync(path.join(RENDERER, 'tracing-page.js'), 'utf8')
|
||||
assert.match(src, /addEventListener\(['"]dsh:rubric-cell-jump['"]/,
|
||||
'tracing-page must register dsh:rubric-cell-jump listener')
|
||||
assert.match(src, /openDrill\(detail\.sessionId\)/,
|
||||
'listener must call openDrill(detail.sessionId) so the jump lands')
|
||||
})
|
||||
|
||||
// -- P0-5 artifact-evolution shows blob-missing banner ---------------------
|
||||
// Static source-string lock (matches artifact-evolution-board.test.js
|
||||
// style for the artifacts.js IIFE — the module lives inside a closure
|
||||
// and we already prove the runtime path there. Here we just prove the
|
||||
// blob-missing banner path exists and gates on the right predicate).
|
||||
test('renderEvolution emits blob-missing banner when any hop lacks blob', () => {
|
||||
const src = fs.readFileSync(path.join(RENDERER, 'artifacts-board.js'), 'utf8')
|
||||
assert.match(src, /artifact-evolution-banner/, 'source must reference the banner class')
|
||||
assert.match(src, /missingBlobs\s*=\s*chain\.some/,
|
||||
'banner must gate on chain.some(missing-blob)')
|
||||
const css = fs.readFileSync(path.join(RENDERER, 'style.css'), 'utf8')
|
||||
assert.match(css, /\.artifact-evolution-banner\s*\{/,
|
||||
'style.css must style the new .artifact-evolution-banner selector')
|
||||
})
|
||||
|
||||
// -- P0-4 artifact seed fixture is inline-reachable ------------------------
|
||||
test('artifact-board-seed.js inlines fixture on window.__dshArtifactBoardSeed', () => {
|
||||
const src = fs.readFileSync(path.join(RENDERER, 'artifact-board-seed.js'), 'utf8')
|
||||
assert.match(src, /window\.__dshArtifactBoardSeed\s*=/, 'seed must attach to window')
|
||||
// Sanity — has multiple kinds and multiple versions.
|
||||
assert.match(src, /"kind":\s*"svg"/, 'seed must include an svg artifact')
|
||||
assert.match(src, /"kind":\s*"html"/, 'seed must include an html artifact')
|
||||
assert.match(src, /"version":\s*3/, 'seed must include multi-version chain (v3+)')
|
||||
// Wired into index.html so the browser actually loads it.
|
||||
const html = fs.readFileSync(path.join(RENDERER, 'index.html'), 'utf8')
|
||||
assert.match(html, /artifact-board-seed\.js/, 'index.html must include artifact-board-seed.js')
|
||||
})
|
||||
|
||||
// -- P1 Lane A: graph binds click on fork nodes + user nodes ---------------
|
||||
test('chat-session-graph binds click on actionable non-turn nodes', () => {
|
||||
const src = fs.readFileSync(path.join(RENDERER, 'chat-session-graph.js'), 'utf8')
|
||||
assert.match(src, /kind === ['"]fork['"] && node\.childSessionId/,
|
||||
'graph must bind click on fork nodes when childSessionId is present')
|
||||
assert.match(src, /kind === ['"]user['"] && node\.seq/,
|
||||
'graph must bind click on user nodes when seq is present')
|
||||
})
|
||||
|
||||
// -- P1 Lane A: renderer stores chatSelectedTurnId + wires user rows -------
|
||||
test('renderer propagates chatSelectedTurnId and wires user rows', () => {
|
||||
const src = fs.readFileSync(path.join(RENDERER, 'renderer.js'), 'utf8')
|
||||
assert.match(src, /state\.chatSelectedTurnId/, 'renderer must store chatSelectedTurnId in state')
|
||||
assert.match(src, /row\.kind === ['"]user['"] && row\.seq/,
|
||||
'renderer drawer onSelect must handle user rows (kind==="user" && seq)')
|
||||
})
|
||||
Reference in New Issue
Block a user