-
-
-
- {
- setEditingFieldId(null);
- setShowAddFieldForm(true);
- }}
- onConfirmAddField={handleConfirmAddField}
- onCancelAddField={() => setShowAddFieldForm(false)}
- onBeginEditField={(fieldId) => {
- setShowAddFieldForm(false);
- setEditingFieldId(fieldId);
- }}
- onSaveFieldEdit={handleSaveFieldEdit}
- onCancelFieldEdit={() => setEditingFieldId(null)}
- />
-
-
+
+
+
+
+
{
+ setEditingFieldId(null);
+ setShowAddFieldForm(true);
+ }}
+ onConfirmAddField={handleConfirmAddField}
+ onCancelAddField={() => setShowAddFieldForm(false)}
+ onBeginEditField={(fieldId) => {
+ setShowAddFieldForm(false);
+ setEditingFieldId(fieldId);
+ }}
+ onSaveFieldEdit={handleSaveFieldEdit}
+ onCancelFieldEdit={() => setEditingFieldId(null)}
+ />
+
);
}
-function ScrollBridge() {
- const { state } = useActiveState();
- const { scrollTo } = useScrollToAnnotation();
- useEffect(() => {
- if (state.activeAnnotationId) {
- scrollTo(state.activeAnnotationId);
- }
- }, [state.activeAnnotationId, scrollTo]);
- return null;
+/**
+ * Evidence sidebar wired for Capture: activate sets binder active evidence
+ * and links to the focused attribute when present.
+ */
+function CaptureEvidenceColumn() {
+ const { state: activeState, setActiveEvidence } = useActiveState();
+ const { bindings } = useBinder();
+
+ const tryLink = useCallback(
+ (evidenceItemId: EvidenceItemId, fieldId: string): boolean => {
+ const existing = bindings
+ .listEvidenceForTarget({ targetType: "form-field", targetId: fieldId })
+ .some((l) => l.evidenceItemId === evidenceItemId);
+ if (existing) return false;
+ bindings.linkEvidenceToTarget({
+ evidenceItemId,
+ target: { targetType: "form-field", targetId: fieldId },
+ });
+ return true;
+ },
+ [bindings],
+ );
+
+ const handleActivate = useCallback(
+ (item: EvidenceItem) => {
+ const annId = item.annotationIds[0] ?? null;
+ setActiveEvidence(item.id, annId);
+
+ const target = activeState.activeTarget;
+ if (target?.targetType === "form-field") {
+ tryLink(item.id, target.targetId);
+ }
+ },
+ [activeState.activeTarget, setActiveEvidence, tryLink],
+ );
+
+ return
;
}
-function FormPane({
+function AttributesPane({
schema,
fieldValues,
onFieldValueChange,
@@ -213,12 +223,6 @@ function FormPane({
const unlinkTick = useEngineEventTick("EvidenceLinkRemoved");
const { state: activeState, setActiveEvidence } = useActiveState();
- useEffect(() => {
- return engine.bus.on("FormFieldActivated", () => {
- publishStripFilter("attached");
- });
- }, [engine]);
-
useEffect(() => {
const target = activeState.activeTarget;
if (!target || activeState.activeEvidenceItemId) return;
@@ -272,9 +276,10 @@ function FormPane({
return (
- Pick a document from the collection to start capturing evidence links.
+ Pick a document from the collection to start linking evidence to attributes.
);
}
-
-function EvidenceStrip({
- fieldLabels,
-}: {
- fieldLabels: ReadonlyMap;
-}) {
- const engine = useEngine();
- const { bindings } = useBinder();
- const { document } = useActiveDocument();
- const createTick = useEngineEventTick("EvidenceItemCreated");
- const updateTick = useEngineEventTick("EvidenceItemUpdated");
- const linkTick = useEngineEventTick("EvidenceLinkCreated");
- const unlinkTick = useEngineEventTick("EvidenceLinkRemoved");
- const { state: activeState, setActiveEvidence, clearActiveEvidence } =
- useActiveState();
-
- const [userFilter, setUserFilter] = useState("all");
- const [sessionFilter, setSessionFilter] = useState(
- null,
- );
-
- const effectiveFilter = sessionFilter ?? userFilter;
-
- useEffect(() => {
- const handler = (e: Event) => {
- setSessionFilter((e as CustomEvent).detail);
- };
- window.addEventListener(STRIP_FILTER_EVENT, handler);
- return () => window.removeEventListener(STRIP_FILTER_EVENT, handler);
- }, []);
-
- useEffect(() => {
- if (!activeState.activeTarget) {
- setSessionFilter(null);
- }
- }, [activeState.activeTarget]);
-
- const allItems = useMemo(() => {
- if (!document) return [];
- void createTick;
- void updateTick;
- void linkTick;
- void unlinkTick;
- return engine.evidence.listByDocument(document.id);
- }, [document, engine, createTick, updateTick, linkTick, unlinkTick]);
-
- const items = useMemo(() => {
- if (effectiveFilter !== "attached" || !activeState.activeTarget) {
- return allItems;
- }
- const links = bindings.listEvidenceForTarget(activeState.activeTarget);
- const ids = new Set(links.map((l) => l.evidenceItemId));
- const attached = allItems.filter((item) => ids.has(item.id));
- return attached.length > 0 ? attached : allItems;
- }, [
- allItems,
- effectiveFilter,
- activeState.activeTarget,
- bindings,
- linkTick,
- unlinkTick,
- ]);
-
- const tryLink = useCallback(
- (evidenceItemId: EvidenceItemId, fieldId: string): boolean => {
- const existing = bindings
- .listEvidenceForTarget({ targetType: "form-field", targetId: fieldId })
- .some((l) => l.evidenceItemId === evidenceItemId);
- if (existing) return false;
- bindings.linkEvidenceToTarget({
- evidenceItemId,
- target: { targetType: "form-field", targetId: fieldId },
- });
- return true;
- },
- [bindings],
- );
-
- const handleCardClick = useCallback(
- (item: EvidenceItem) => {
- const annId = item.annotationIds[0] ?? null;
- setActiveEvidence(item.id, annId);
-
- const target = activeState.activeTarget;
- if (target?.targetType === "form-field") {
- tryLink(item.id, target.targetId);
- }
- },
- [activeState.activeTarget, setActiveEvidence, tryLink],
- );
-
- const handleUnlink = useCallback(
- (link: EvidenceLink) => {
- bindings.unlinkEvidence(link.id);
- if (
- activeState.activeEvidenceItemId === link.evidenceItemId &&
- activeState.activeTarget?.targetType === link.targetType &&
- activeState.activeTarget?.targetId === link.targetId
- ) {
- clearActiveEvidence();
- }
- },
- [bindings, activeState, clearActiveEvidence],
- );
-
- if (!document) return null;
-
- return (
-
-
- Show:
- {
- setUserFilter("all");
- setSessionFilter(null);
- }}
- />
- {
- setUserFilter("attached");
- setSessionFilter(null);
- }}
- />
-
-
- {items.length === 0 && (
-
- {effectiveFilter === "attached"
- ? "No evidence linked to the active field."
- : "No evidence yet. Switch to Review mode to capture a passage."}
-
- )}
- {items.map((item) => (
-
handleCardClick(item)}
- onUnlink={handleUnlink}
- />
- ))}
-
-
- );
-}
-
-function FilterToggle({
- label,
- active,
- onClick,
-}: {
- label: string;
- active: boolean;
- onClick: () => void;
-}) {
- return (
-
- );
-}
-
-function EvidenceStripCard({
- item,
- isActive,
- links,
- fieldLabels,
- onClick,
- onUnlink,
-}: {
- item: EvidenceItem;
- isActive: boolean;
- links: readonly EvidenceLink[];
- fieldLabels: ReadonlyMap;
- onClick: () => void;
- onUnlink: (link: EvidenceLink) => void;
-}) {
- const engine = useEngine();
- const ref = useRef(null);
- useRegisterRect("evidence-card", item.id, ref);
-
- const firstAnn = item.annotationIds[0]
- ? engine.annotations.get(item.annotationIds[0])
- : null;
- const quote = firstAnn?.quote ?? "(no quote)";
-
- const formLinks = links.filter((l) => l.targetType === "form-field");
-
- return (
-
- {formLinks.length > 0 && (
-
- {formLinks.map((link) => {
- const label = fieldLabels.get(link.targetId) ?? link.targetId;
- return (
-
- );
- })}
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/forms/ScrollBridge.tsx b/src/app/forms/ScrollBridge.tsx
new file mode 100644
index 0000000..153b4b5
--- /dev/null
+++ b/src/app/forms/ScrollBridge.tsx
@@ -0,0 +1,19 @@
+/**
+ * ScrollBridge — when binder active annotation changes, scroll the viewer.
+ */
+
+import { useEffect } from "react";
+
+import { useActiveState } from "@binder/index";
+import { useScrollToAnnotation } from "@work/index";
+
+export function ScrollBridge() {
+ const { state } = useActiveState();
+ const { scrollTo } = useScrollToAnnotation();
+ useEffect(() => {
+ if (state.activeAnnotationId) {
+ scrollTo(state.activeAnnotationId);
+ }
+ }, [state.activeAnnotationId, scrollTo]);
+ return null;
+}
diff --git a/src/app/forms/demo-schema.ts b/src/app/forms/demo-schema.ts
index 386f2f2..3e28fd1 100644
--- a/src/app/forms/demo-schema.ts
+++ b/src/app/forms/demo-schema.ts
@@ -1,10 +1,11 @@
/**
- * Demo form schema for CE-WP-0003 (the form-binding slice).
+ * Demo attributes schema for Capture mode (CE-WP-0003 form-binding slice;
+ * CE-WP-0010 renames the user-facing concept from "form" to "attributes").
*
* Deliberately minimal: text, textarea, date. JSON Schema is **not** used
- * here — that's deferred to a later ADR. The MVP form's only job is to
- * render a handful of fields and accept evidence links so the visual-guide
- * round-trip can be exercised end-to-end.
+ * here — that's deferred to a later ADR. The schema's only job is to render
+ * a handful of key/value attributes and accept evidence links so the
+ * visual-guide round-trip can be exercised end-to-end.
*/
export type FormFieldSchema =
@@ -20,7 +21,7 @@ export interface FormSchema {
export const DEMO_SCHEMA: FormSchema = {
id: "demo-form",
- title: "Demo evidence-backed form",
+ title: "Attributes",
fields: [
{ type: "textarea", id: "summary", label: "Summary of the matter" },
{ type: "date", id: "deadline", label: "Key deadline" },
diff --git a/tests/integration/capture-session-persist.dom.test.tsx b/tests/integration/capture-session-persist.dom.test.tsx
index ef4c267..dfffa1c 100644
--- a/tests/integration/capture-session-persist.dom.test.tsx
+++ b/tests/integration/capture-session-persist.dom.test.tsx
@@ -57,9 +57,9 @@ describe("Capture session persistence", () => {
const user = userEvent.setup();
const first = await loadApp();
- const summary = screen.getByLabelText("Summary of the matter");
+ const summary = screen.getByRole("textbox", { name: /Summary of the matter/ });
await user.type(summary, "Persisted summary text");
- await user.click(screen.getByLabelText("Disputed amount"));
+ await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
await waitFor(() => {
const stored = Object.values(globalThis.localStorage ?? {}).join("");
@@ -70,7 +70,7 @@ describe("Capture session persistence", () => {
await loadApp();
- const restored = screen.getByLabelText("Summary of the matter") as HTMLTextAreaElement;
+ const restored = screen.getByRole("textbox", { name: /Summary of the matter/ }) as HTMLTextAreaElement;
await waitFor(() => {
expect(restored.value).toBe("Persisted summary text");
});
@@ -84,7 +84,7 @@ describe("Capture session persistence", () => {
const user = userEvent.setup();
await loadApp();
- await user.type(screen.getByLabelText("Disputed amount"), "EUR 500");
+ await user.type(screen.getByRole("textbox", { name: /Disputed amount/ }), "EUR 500");
await waitFor(() => {
const keys = Object.keys(globalThis.localStorage ?? {});
diff --git a/tests/integration/forms-active-cycling.dom.test.tsx b/tests/integration/forms-active-cycling.dom.test.tsx
index 62d0520..3f36971 100644
--- a/tests/integration/forms-active-cycling.dom.test.tsx
+++ b/tests/integration/forms-active-cycling.dom.test.tsx
@@ -144,7 +144,7 @@ describe("FormsApp — active-evidence cycling (CE-WP-0003-T06)", () => {
});
// Focus Summary, then click strip card → link gets created.
- const summaryField = screen.getByLabelText("Summary of the matter");
+ const summaryField = screen.getByRole("textbox", { name: /Summary of the matter/ });
await user.click(summaryField);
await user.click(stripCard);
@@ -167,7 +167,7 @@ describe("FormsApp — active-evidence cycling (CE-WP-0003-T06)", () => {
//
// Note: clicking the same field doesn't fire onFocus if it's already
// focused. Move focus elsewhere first, then back.
- await user.click(screen.getByLabelText("Disputed amount"));
+ await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
await user.click(summaryField);
// The strip card for the linked evidence has aria-current="true".
diff --git a/tests/integration/forms-field-edit.dom.test.tsx b/tests/integration/forms-field-edit.dom.test.tsx
index 19dc5f1..252caad 100644
--- a/tests/integration/forms-field-edit.dom.test.tsx
+++ b/tests/integration/forms-field-edit.dom.test.tsx
@@ -104,13 +104,22 @@ describe("Capture — field add/edit UX (CE-WP-0007-T10/T11)", () => {
await user.selectOptions(screen.getByTestId("field-add-type-select"), "date");
await user.click(screen.getByTestId("field-add-save"));
- const hearingInput = await screen.findByLabelText("Hearing date");
+ const hearingInput = await waitFor(() => {
+ const inputs = Array.from(
+ document.querySelectorAll('input[type="date"]'),
+ ) as HTMLInputElement[];
+ const match = inputs.find((el) =>
+ (el.labels?.[0]?.textContent ?? "").includes("Hearing date"),
+ );
+ if (!match) throw new Error("expected Hearing date input");
+ return match;
+ });
expect(hearingInput.getAttribute("type")).toBe("date");
},
);
it(
- "edits an existing field label and type via the pencil icon",
+ "edits an existing attribute key and type via the pencil icon",
{ timeout: 15000 },
async () => {
const user = userEvent.setup();
@@ -130,8 +139,8 @@ describe("Capture — field add/edit UX (CE-WP-0007-T10/T11)", () => {
);
await user.click(screen.getByTestId("field-edit-summary-save"));
- await screen.findByLabelText("Matter summary");
- expect(screen.queryByLabelText("Summary of the matter")).toBeNull();
+ await screen.findByRole("textbox", { name: /Matter summary/ });
+ expect(screen.queryByRole("textbox", { name: /Summary of the matter/ })).toBeNull();
},
);
@@ -156,7 +165,7 @@ describe("Capture — field add/edit UX (CE-WP-0007-T10/T11)", () => {
await user.type(addLabel, "Custom slot");
await user.click(screen.getByTestId("field-add-save"));
- const customField = await screen.findByLabelText("Custom slot");
+ const customField = await screen.findByRole("textbox", { name: /Custom slot/ });
await user.click(customField);
const stripCard = screen.getByRole("button", { name: /Link to custom field/ });
diff --git a/tests/integration/forms-field-values.dom.test.tsx b/tests/integration/forms-field-values.dom.test.tsx
index 1e3d026..3c617f4 100644
--- a/tests/integration/forms-field-values.dom.test.tsx
+++ b/tests/integration/forms-field-values.dom.test.tsx
@@ -51,9 +51,9 @@ describe("Capture — field value persistence (CE-WP-0008-T01)", () => {
const user = userEvent.setup();
await loadApp();
- const summary = screen.getByLabelText("Summary of the matter");
+ const summary = screen.getByRole("textbox", { name: /Summary of the matter/ });
await user.type(summary, "Tenant owes arrears");
- await user.click(screen.getByLabelText("Disputed amount"));
+ await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
await user.click(summary);
expect((summary as HTMLTextAreaElement).value).toBe("Tenant owes arrears");
@@ -63,10 +63,10 @@ describe("Capture — field value persistence (CE-WP-0008-T01)", () => {
const user = userEvent.setup();
await loadApp();
- const deadline = screen.getByLabelText("Key deadline") as HTMLInputElement;
+ const deadline = document.querySelector("#field-deadline") as HTMLInputElement;
await user.clear(deadline);
await user.type(deadline, "2026-12-15");
- await user.click(screen.getByLabelText("Disputed amount"));
+ await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
expect(deadline.value).toBe("2026-12-15");
});
diff --git a/tests/integration/forms-link-flow.dom.test.tsx b/tests/integration/forms-link-flow.dom.test.tsx
index b209fed..0529866 100644
--- a/tests/integration/forms-link-flow.dom.test.tsx
+++ b/tests/integration/forms-link-flow.dom.test.tsx
@@ -109,7 +109,7 @@ describe("FormsApp — focus-gated linking (CE-WP-0007-T02)", () => {
await user.click(screen.getByRole("button", { name: "Capture" }));
await screen.findByRole("button", { name: /Field-first link test/ });
- await user.click(screen.getByLabelText("Disputed amount"));
+ await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
const stripCard = screen.getByRole("button", {
name: /Field-first link test/,
});
diff --git a/tests/integration/forms-overlay-e2e.dom.test.tsx b/tests/integration/forms-overlay-e2e.dom.test.tsx
index 92e72d9..9aa6275 100644
--- a/tests/integration/forms-overlay-e2e.dom.test.tsx
+++ b/tests/integration/forms-overlay-e2e.dom.test.tsx
@@ -147,22 +147,21 @@ describe("CE-WP-0003-T08 — PRD scenario steps 5-9 end-to-end", () => {
// CE-WP-0005: route is now session-scoped.
expect(window.location.hash).toMatch(/^#\/s\/sess_[^/]+\/forms\/demo$/);
- // Step 6: focus summary, then click the strip card to link directly.
- const summaryField = screen.getByLabelText("Summary of the matter");
+ // Step 6: focus summary attribute, then click the Evidence card to link.
+ const summaryField = screen.getByRole("textbox", { name: /Summary of the matter/ });
await user.click(summaryField);
- const stripCard = await screen.findByRole("button", {
+ const evidenceCard = await screen.findByRole("button", {
name: /Overlay E2E evidence/,
});
- await user.click(stripCard);
+ await user.click(evidenceCard);
// Move focus elsewhere and back to re-fire focus on summary so that
// ActiveStateProvider triggers focus-target (the previous click that
// created the link consumed the staged state).
- await user.click(screen.getByLabelText("Disputed amount"));
+ await user.click(screen.getByRole("textbox", { name: /Disputed amount/ }));
await user.click(summaryField);
- // Step 8: aria-current on field row, chip, and (via the active
- // state) the strip card.
+ // Step 8: aria-current on attribute row and active evidence card.
await waitFor(() => {
const fieldRow = document.querySelector(
'[data-field-id="summary"][aria-current="true"]',
@@ -173,7 +172,7 @@ describe("CE-WP-0003-T08 — PRD scenario steps 5-9 end-to-end", () => {
expect(activeCard).not.toBeNull();
expect(activeCard!.textContent).toMatch(/Overlay E2E evidence/);
- // Step 9: SVG overlay renders 2 paths (field→card + card→highlight).
+ // Step 9: SVG overlay renders 2 paths (attribute→card + card→highlight).
// HighlightRectBridge registers via the mocked getHighlightClientRects.
await waitFor(() => {
const svg = document.querySelector('[data-testid="visual-guide-overlay"]');
diff --git a/tests/integration/forms-strip-filter.dom.test.tsx b/tests/integration/forms-strip-filter.dom.test.tsx
index 0e057a9..c105faf 100644
--- a/tests/integration/forms-strip-filter.dom.test.tsx
+++ b/tests/integration/forms-strip-filter.dom.test.tsx
@@ -1,5 +1,6 @@
/**
- * CE-WP-0006-T04 — evidence strip filter (all vs attached-to-active-field).
+ * CE-WP-0010-T03 — Evidence column text filter (≥3 characters).
+ * Replaces the CE-WP-0006 bottom-strip All/Attached toggles (strip removed).
*/
// @vitest-environment happy-dom
@@ -70,7 +71,7 @@ async function captureAndSave(user: ReturnType, commenta
await screen.findByText(new RegExp(commentary));
}
-describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
+describe("EvidenceSidebar — text filter (CE-WP-0010-T03)", () => {
beforeEach(() => {
viewerSnapshot.onSelectionCaptured = null;
globalThis.localStorage?.clear();
@@ -78,7 +79,7 @@ describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
history.replaceState(null, "", window.location.pathname);
}
seedSessionWithDoc({
- sessionName: "T04-filter",
+ sessionName: "T03-filter",
documentTitle: FIXTURE.filename,
canonicalText: SYNTHETIC_CANONICAL,
});
@@ -90,7 +91,7 @@ describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
});
it(
- "narrows to attached evidence on field focus and restores on All toggle",
+ "filters evidence cards only when the query has 3+ characters",
{ timeout: 20000 },
async () => {
const user = userEvent.setup();
@@ -99,26 +100,20 @@ describe("FormsApp — evidence strip filter (CE-WP-0006-T04)", () => {
await captureAndSave(user, "Linked to summary");
await captureAndSave(user, "Unlinked orphan");
- await user.click(screen.getByRole("button", { name: "Capture" }));
- const linkedCard = await screen.findByRole("button", {
- name: /Linked to summary/,
- });
+ expect(screen.getByRole("button", { name: /Linked to summary/ })).toBeTruthy();
expect(screen.getByRole("button", { name: /Unlinked orphan/ })).toBeTruthy();
- // Link the first item to summary; field focus keeps attached filter active.
- await user.click(screen.getByLabelText("Summary of the matter"));
- await user.click(linkedCard);
- await waitFor(() => {
- expect(screen.getByTestId("field-summary-chip").textContent).toMatch(
- /1 evidence/,
- );
- expect(screen.queryByRole("button", { name: /Unlinked orphan/ })).toBeNull();
- });
+ const filter = screen.getByTestId("evidence-filter");
+ await user.type(filter, "Un");
+ // Under 3 chars — both still visible
+ expect(screen.getByRole("button", { name: /Linked to summary/ })).toBeTruthy();
+ expect(screen.getByRole("button", { name: /Unlinked orphan/ })).toBeTruthy();
- await user.click(screen.getByRole("button", { name: "All" }));
+ await user.type(filter, "l"); // "Unl"
await waitFor(() => {
+ expect(screen.queryByRole("button", { name: /Linked to summary/ })).toBeNull();
expect(screen.getByRole("button", { name: /Unlinked orphan/ })).toBeTruthy();
});
},
);
-});
\ No newline at end of file
+});
diff --git a/workplans/CE-WP-0010-annotate-attributes-ux.md b/workplans/CE-WP-0010-annotate-attributes-ux.md
index 9d9f84d..0d3e5ea 100644
--- a/workplans/CE-WP-0010-annotate-attributes-ux.md
+++ b/workplans/CE-WP-0010-annotate-attributes-ux.md
@@ -7,7 +7,7 @@ repo: citation-evidence
repo_id: a677c189-b4e2-4f2a-9e48-faa482c277e6
topic_slug: citation_evidence_mvp
topic_id: cee7bedf-2b48-46ef-8601-006474f2ad7a
-status: proposed
+status: finished
owner: codex
created: "2026-07-30"
updated: "2026-07-30"
@@ -114,7 +114,7 @@ T07 (tests + copy audit)
```task
id: CE-WP-0010-T01
-status: todo
+status: done
priority: high
state_hub_task_id: "acacc281-7cc3-4833-a123-593494cafe24"
```
@@ -139,7 +139,7 @@ are updated in the same task.
```task
id: CE-WP-0010-T02
-status: todo
+status: done
priority: high
state_hub_task_id: "f8538031-3916-4f3b-a66c-2a5fd1d575f7"
```
@@ -174,7 +174,7 @@ state_hub_task_id: "f8538031-3916-4f3b-a66c-2a5fd1d575f7"
```task
id: CE-WP-0010-T03
-status: todo
+status: done
priority: high
state_hub_task_id: "5ba0e6dd-e2c9-4167-87fb-2ac7a7d81933"
```
@@ -211,7 +211,7 @@ for MVP).
```task
id: CE-WP-0010-T04
-status: todo
+status: done
priority: high
depends_on: [T02]
state_hub_task_id: "3ca351b4-a039-48ad-a2a1-d95cc202b04f"
@@ -234,7 +234,7 @@ Same interaction model as T03, next to the **Attributes** caption:
```task
id: CE-WP-0010-T05
-status: todo
+status: done
priority: critical
depends_on: [T02, T03]
state_hub_task_id: "45e4a981-9985-49cd-bdbf-4ce8e29ae243"
@@ -287,7 +287,7 @@ sidebar experience and duplicates cards under the page.
```task
id: CE-WP-0010-T06
-status: todo
+status: done
priority: high
depends_on: [T05]
state_hub_task_id: "3451fe43-2909-4bf6-aa7c-72805239245b"
@@ -327,7 +327,7 @@ rect-registry + Overlay stack from CE-WP-0003 where possible.
```task
id: CE-WP-0010-T07
-status: todo
+status: done
priority: medium
depends_on: [T01, T02, T03, T04, T05, T06]
state_hub_task_id: "cc6802be-c910-4b5b-aa36-0ba970aff4c4"