Implement CE-WP-0004 T01-T05: citation card export (Markdown + HTML)

Per-evidence-item export: click Export → Copy as Markdown / Copy as HTML
writes a portable citation card to the clipboard. Cmd/Ctrl+Shift+C
exports the active evidence as Markdown.

- ADR-0007 locks the Markdown + HTML output formats.
- New shared types: CitationCard, openContextUrl(), resolveSourceLabel().
- Engine renderers under src/engine/rendering/: renderCitationCardMarkdown,
  renderCitationCardHtml — snapshot-tested, escape-safe, BEM classes for HTML.
- src/work/useExportEvidence.ts wires engine + renderers + clipboard.
- EvidenceSidebar gains an Export popover per row + auto-dismissing toast.
- E2E test (tests/integration/citation-card-export-e2e.dom.test.tsx)
  walks PRD scenario steps 10-11 and asserts the clipboard payload.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 14:43:17 +02:00
parent 8607c252c4
commit 8632f7b04a
19 changed files with 1617 additions and 35 deletions

View File

@@ -0,0 +1,32 @@
/**
* Unit tests for `openContextUrl()`. Locks the shape from
* `wiki/ArchitectureOverview.md` §14.3.
*/
import { describe, expect, it } from "vitest";
import type { AnnotationId, DocumentId } from "./ids";
import { openContextUrl } from "./open-context-url";
const DOC = "doc_abc-123" as DocumentId;
const ANN = "ann_def-456" as AnnotationId;
describe("openContextUrl()", () => {
it("produces the canonical /viewer?document=…&annotation=… shape", () => {
expect(openContextUrl({ documentId: DOC, annotationId: ANN })).toBe(
"/viewer?document=doc_abc-123&annotation=ann_def-456",
);
});
it("percent-encodes reserved characters in ids", () => {
const ugly = "doc with spaces&=#" as DocumentId;
const url = openContextUrl({ documentId: ugly, annotationId: ANN });
expect(url).toBe(
"/viewer?document=doc%20with%20spaces%26%3D%23&annotation=ann_def-456",
);
// round-trip
const params = new URL(url, "https://example.test").searchParams;
expect(params.get("document")).toBe("doc with spaces&=#");
expect(params.get("annotation")).toBe("ann_def-456");
});
});