Files
deepseek-harness/examples/desktop/docs/qa-artifact-evolution/fixture.html
T
ZiyaZhang c8f5ca3d5b feat(desktop): artifact evolution chain + Board/Timeline views
Adds a version-evolution capability to the Artifacts pane:
  - Evolution chain: LCS-based diff across successive versions of the
    same logical artifact; when a blob is missing, the chain honestly
    surfaces a placeholder rather than fabricating diff content.
  - Board / Timeline tab switcher on the Artifacts page.
  - Auto-grouping into 5 kinds so the Board reads at a glance.

Files:
  src/renderer/artifacts-board.js       — new Board / Timeline / Evolution views
  src/renderer/artifacts.js             — wire Board tab + evolution rail
  src/renderer/index.html               — script tag + tab markup
  src/renderer/style.css                — tail append (~200 lines)
  test/artifact-evolution-board.test.js — 23 new tests
  docs/artifact-board-fixture.json      — fixture seed
  docs/qa-artifact-evolution/           — 3 QA screenshots + fixture.html
  scripts/qa-cdp-shoot-artifact-v2.mjs  — QA capture harness

Merged as source-repo test-real @ 7279870 (with lane-side conflict
resolution against test-real @ c1d93aa: style.css tail-append union;
independent of Lanes C/A/D CSS sections).

Test suite: 1727/1727 pass (+23 over Lane C+A+D baseline of 1704).

Follow-up L-3 (in upstream ledger): runtime ArtifactServer to grow
a snapshot store so the "missing blob" fallback becomes rare.
2026-07-19 01:10:53 -07:00

100 lines
4.3 KiB
HTML

<!DOCTYPE html>
<!-- Reproducible fixture for the lane-artifact-v2 change (version
evolution strip + Board / Timeline views). Loads the fixture data
from docs/artifact-board-fixture.json and replays every entry
through window.__cb (the onArtifact bridge) in order, so:
* Auto-group + L0 rows populate the List view.
* Multi-version entries (session.md v1→v3, landing.html v1→v2,
config.json v1→v2) exercise the evolution strip diffs.
* Every kind bucket (md/html/svg/json/code/other) surfaces in the
Board view.
Screenshots for docs/qa-artifact-evolution/ are taken with the
panel in each of the three views + the evolution strip opened. -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>lane-artifact-v2 fixture — Board + evolution</title>
<link rel="stylesheet" href="../../src/renderer/style.css">
<style>
/* Bare-minimum host chrome. */
html, body { height: 100%; margin: 0; }
body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; }
.app { display: flex; height: 100vh; }
.main { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.header {
padding: 10px 16px; border-bottom: 1px solid var(--border);
font-size: 13px; color: var(--muted);
display: flex; gap: 8px; align-items: center;
}
.header .fixture-controls button {
background: var(--bg-elev-2); color: var(--text);
border: 1px solid var(--border); padding: 3px 10px;
border-radius: 4px; font-size: 11px; cursor: pointer;
}
.header .fixture-controls button:hover { border-color: var(--accent); }
#stream {
flex: 1; padding: 12px; overflow-y: auto;
display: flex; flex-direction: column; gap: 12px;
}
/* Silence the pulse + arrival flash so screenshots capture the
resting state, matching the qa-artifact-compact convention. */
.artifact-live-dot { animation: none !important; }
.artifact-card.artifact-flash { border-color: var(--border) !important; background: var(--bg-elev) !important; }
</style>
</head>
<body>
<div class="app">
<div class="main">
<div class="header">
<span>Fixture: 3 multi-version + 4 single-version artifacts across 5 kinds</span>
<span class="fixture-controls" style="margin-left: auto; display: flex; gap: 6px;">
<button data-view="list">List</button>
<button data-view="board">Board</button>
<button data-view="timeline">Timeline</button>
<button id="open-evolution">Open evolution: session.md</button>
</span>
</div>
<div id="stream" class="stream"></div>
</div>
</div>
<!-- Fake bridge — artifacts.js only touches window.dsh.onArtifact +
window.dsh.openArtifact + window.dsh.mockArtifact. -->
<script>
window.dsh = {
onArtifact: (cb) => { window.__cb = cb },
openArtifact: async () => ({ ok: true }),
mockArtifact: async () => {},
}
</script>
<script src="../../src/renderer/artifacts-board.js"></script>
<script src="../../src/renderer/artifacts.js"></script>
<script>
// Replay the fixture through the same onArtifact seam the real IPC
// event uses. Because the artifacts.js history tracker de-dups on
// version, feeding multi-version entries in order produces exactly
// the same chain a real session would.
async function main() {
const res = await fetch('../artifact-board-fixture.json')
const data = await res.json()
for (const entry of data.artifacts) {
window.__cb(entry)
}
// Wire the header buttons to the exposed switchView.
const api = window.__dshArtifacts
for (const btn of document.querySelectorAll('.fixture-controls button[data-view]')) {
btn.addEventListener('click', () => api.switchView(btn.dataset.view))
}
// Convenience: pop the version chip on session.md so QA can
// snap the evolution strip without a manual click search.
document.getElementById('open-evolution').addEventListener('click', () => {
const card = document.querySelector('.artifact-card[data-artifact-id="session.md"]')
if (!card) return
const chip = card.querySelector('.artifact-version')
if (chip) chip.click()
})
}
main()
</script>
</body>
</html>