Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions PR_POLICY_BODY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Summary

- Re-declare dark body/heading ink and companion roles on `.dark .ckb-v2` after the cascade port so light `.ckb-v2` matching inside dark subtrees cannot resolve light ink on dark surfaces.
- Merge `origin/main` to clear DIRTY mergeability (prefer main’s post-#1538/#1541 component copies; keep this tip’s dark-ink contract + HCM transparent overlay-backdrop rationale).
- Tabs: `aria-controls` only on the selected tab when a panel is owned (unselected tabs no longer point at missing IDs).
- Tooltip: compose mouse/focus/keydown handlers with the child’s existing handlers instead of replacing them.

RAG impact: no retrieval behaviour change — design-system token dark-cascade fix and unadopted UI component a11y only; no file under `src/lib/rag/**`, clinical-search, retrieval-selection, ranking, eval harness, golden fixtures, or retrieval RPCs is touched.

## Verification

- [x] `npm run verify:pr-local`
- [x] focused: `vitest` `tests/ckb-v2-token-contract.test.ts` + `tests/ui-v2-components.dom.test.tsx` — 35 passed
- [x] `npm run verify:cheap` — 457 files / 4782 passed
Comment thread
BigSimmo marked this conversation as resolved.
- UI verification not run: no production surface adopts `.ckb-v2` / these components yet; phone-chrome and visual journeys unchanged. Prefer CI Production UI on this tip.

## Risk and rollout

- Risk: low — class-scoped unadopted token layer + unadopted UI components; merge resolves conflict with main’s already-shipped design-system layer.
- Rollback: revert the squash / tip commits; no schema, data, or provider surface.
- Provider or production effects: None.

## Clinical Governance Preflight

- [x] Source-backed claims still require linked source verification before clinical use
- [x] No patient-identifiable document workflow was introduced or expanded without explicit governance approval
- [x] Supabase target remains `Clinical KB Database` (`sjrfecxgysukkwxsowpy`)
- [x] Service-role keys and private document access remain server-only
- [x] Demo/synthetic content remains clearly separated from real clinical sources
- [x] Source metadata, review status, and outdated/unknown-source behavior remain conservative
- [x] Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

## Notes

- Remaining playbook work stays in later tranches (SPEC §13).
- Companion design-system docs already on main via #1531 / #1537 / #1538 / #1541.
3 changes: 2 additions & 1 deletion docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,12 +561,13 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-07-31 | codex/merge-privacy-safe-error-tracking-implementation | fab9a9ec5181eb6ef3f8f43f303c39fa87cc1f6c | bugbot-review | no-p0-p1; empty/partial-meta-neutralized; sentry-init-failopen; gitleaks+pr-policy-body; merge-tree-clean; PR-left-closed | vitest:46-pass-focused; typecheck:pass; gitleaks-range:clean; merge-tree:clean; verify:pr-local:codex-cloud-fixed |
| 2026-07-31 | codex/address-performance-issues-in-package | aa8c2dfb1406a7a3f74745d20f2b370c75b55719 | PR #1489 review+bugbot+fix+heavy | synced main (#1478 behind-but-clean); fixed docs inventory + #117 stale hashed paths; verify:cheap 4683 passed; verify:pr-local build+bundle-budget+RAG fixtures green; typecheck clean | verify:cheap: 448 files / 4683 passed; verify:pr-local: Client bundle secret surface check passed + Offline RAG fixture validation passed (36 golden cases); check:bundle-budget: within tolerance + done; format:check: All matched files use Prettier code style!; merge-tree origin/main clean |
| 2026-07-31 | claude/issues-writer-cli (PR #1524) | 83dec1f5a36d577c87ee9a5382ef8431d197d93d | PR #1524 review+bugbot+fix | before: dirty/CONFLICTING vs main (outstanding-issues.md + scripts-index.md), missing pull_request CI, 0 review threads, NOT REVIEWED. after: merged origin/main (prefer main queues; renumbered this PR collision-free-ids note #159→#168, next-id=169); fixed wrong skill/writer cite #154→#156/#168; no other P0–P2 writer defects; 0 threads. Residual: concurrent id RMW (#156/#168) still open. | check:outstanding-issues pass (166 rows, next-id=169); outstanding-issues.mjs --self-test pass; vitest tests/outstanding-issues-writer.test.ts 8/8; merge-tree clean vs origin/main; format clean; no provider-backed checks |
| 2026-07-31 | PR #1536 | f92ad5ea35a9c68c2382fd94c56cfbb3deffb185 | documents filter panel + library naming | Handoff. Made the smart-tag facet panel reachable (its only mount was gated on a selection nothing could make), merged source-type filtering into it, and renamed the library control off 'Open source filters'. Kept the ribbon library button after a browser run showed the action-menu route clears the query via onSearchModeChange. Ledger #176 filed for the separately-inert command-scope system. | verify:cheap exit 0 (450 files / 4710 tests); ui-smoke.spec.ts chromium 93 passed 1 failed, the failure pre-existing under Chromium 1194 vs pinned 1228; mutation-tested (old gate fails all 6 new DOM tests) |
| 2026-07-31 | claude/fable-implementation-fc937c | 8401138cf7fc2c02d2fad54a7960bbb66d1fd7ae | design-system doc set (SPEC/TOKENS/COMPONENTS/DECISIONS/GATES) + sentry-merge repair (instrumentation syntax, ui-primitives icon revert, sentry options, formatting) | handoff: PR opened for review; auto-merge not armed (clinical-risk paths) | tsc 0 errors; vitest ui-primitives.dom+icon-button.dom 7/7; token contracts 47/47 (design branch); prettier whole-tree; docs:check-links 1486; eslint 0 errors |
| 2026-07-31 | claude/fable-implementation-fc937c | 14e3c3773d8be0a771673248e898c9bcde08b8dc | pr-1531 review-and-fix | fixed-sentry-privacy-blockers; merge-tree-clean; threads-cleared | vitest:error-tracking+env-sentry 6/6; tsc --noEmit 0 |
| 2026-07-31 | claude/fable-implementation-fc937c | e87d4e583cec7277f6c1ff2fc5c3a08b2d1b37aa | pr-1531 unblock | fixed static-pr eslint anon-export + build bundle-budget (-client Sentry); merge-tree-clean; 0 blocking threads | eslint next.config --max-warnings 0; check:bundle-budget within tolerance (1394.0/1278.6 KiB gzip); vitest error-tracking+env-sentry 6/6 |
| 2026-07-31 | 1489 | 67d5cb91083f9b0e9d3017816cbf68abab102688 | PR 1489 review — Therapy startup/sidebar perf, catalogue split, bundle-budget, phone-chrome | approved with follow-ups; merged 945148251. No P0/P1. Findings fixed on claude/pr-1489-review-786e01: inferred modality mislabelled ECT/rTMS as ACT and Psychoanalysis as CBT (pre-existing on main); hashed catalogue assets never pruned (2 stranded in-PR); classifyPullRequestFiles returned clinicalRisk:false for 205 clinical records; viewportHeightChanged guard outranked topRevealOffset; guard keyed innerHeight not visualViewport; sk-proj- keys unescaped; bundle-budget step timeout 3m too tight. Bundling note: operationalRisk+clinicalRisk in one squash, so no per-item revert. | verify:cheap static gates pass; lint pass; typecheck exit 0; vitest 449 files/4700 pass; verify:phone-chrome contracts 116 pass + focused browser 13 pass; verify:ui 342 pass/2 fail, both pass isolated (composer hero-vs-dock hydration race, no position: assignment in use-hide-on-scroll) |
| 2026-07-31 | claude/fable-implementation-fc937c | 2147572428271278462db2aac169a5c3c0a2bcc7 | pr-1531 unblock | reverted CodeRabbit autofix (next.config env import broke build; prettier docs); merged origin/main behind-but-clean; merge-tree-clean | prettier design-system+next.config; eslint next.config --max-warnings 0; merge-tree clean vs origin/main |
| 2026-07-31 | claude/clinical-kb-design-system-333a69 | d51b2b7ac8969e21aae9c328c525ae22dccbcccd | v2 design-system layer: tokens+26 components, C1 reconciliation, cascade port, HCM block, danger contrast, AsyncButton type | handoff: PR opened, squash auto-merge armed on green | vitest contracts 67/67 pre-merge, 74/74 post-merge; export baseline tsc/lint/verify:ui 344 recorded |
| 2026-07-31 | PR #1536 | f92ad5ea35a9c68c2382fd94c56cfbb3deffb185 | documents filter panel + library naming | Handoff. Made the smart-tag facet panel reachable (its only mount was gated on a selection nothing could make), merged source-type filtering into it, and renamed the library control off 'Open source filters'. Kept the ribbon library button after a browser run showed the action-menu route clears the query via onSearchModeChange. Ledger #176 filed for the separately-inert command-scope system. | verify:cheap exit 0 (450 files / 4710 tests); ui-smoke.spec.ts chromium 93 passed 1 failed, the failure pre-existing under Chromium 1194 vs pinned 1228; mutation-tested (old gate fails all 6 new DOM tests) |
| 2026-07-31 | cursor/sentry-db-query-tracing-0546 | 0dfb7a35a3a2623794a2d3e4c657268018604d18 | pr-1540-unblock | fixed static-pr knip unlisted @sentry/core; merge-tree clean; no blocking threads | check:knip:pass; vitest:error-tracking:5/5; tsc:pass; merge-tree:clean |
| 2026-07-31 | PR #1542 | c028450b31441e272a04471292cd162f9c8c826b | documents filter sheet (C1) | Handoff. Moved the documents filter panel into ui/sheet.tsx: bottom sheet below sm, centred dialog above, header/footer split into the sheet slots, trigger upgraded to a dialog opener with aria-controls via Sheet's new id prop. Branch restarted from origin/main after PR #1536 merged and its branch was deleted; C1 cherry-picked on top rather than stacked on merged history. | verify:cheap exit 0 (457 files / 4780 tests); document-filter-panel.dom.test.tsx 9 passed incl. new dialog+Escape cases; ui-smoke documents journey 1 passed chromium; verify:phone-chrome stages green except the pre-existing PDF-canvas failure, focused-browser stage run directly 7 passed |
| 2026-07-31 | claude/sentry-agent-monitoring-eri94v | 38378ac78c0b288b6e93b638019653001f375042 | Sentry AI agent monitoring (OpenAI wrap, gen_ai scrubber allowlist, conversation id) | pass — metadata-only instrumentation; privacy boundary preserved | verify:pr-local,verify:cheap,typecheck,vitest 458 files green |
14 changes: 14 additions & 0 deletions src/app/ckb-v2-tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@
--border-strong: #47505a;
--border-lux: #3a424a;

/* Required since the cascade port: the light block now matches inside dark
subtrees too, so every light-declared role must be re-declared here or it
resolves to light ink on dark surfaces. Values mirror the live .dark layer
these previously fell through to. */
--text-heading: #fbfcfd;
--text: #f4f6f8;
--disabled: #626c7a;
--text-muted: #a8b2bd;
--text-soft: #7d8792;
--decoration-soft: #7d8792;
Expand All @@ -269,6 +276,13 @@
--command-active: #1d6fb8;
--command-contrast: #06121e;

--clinical-accent-soft: #123556;
--clinical-accent-border: #235a7d;
--primary-soft: #123556;
--glow-primary: 0 0 0 1px color-mix(in srgb, var(--clinical-accent) 45%, transparent), 0 8px 24px rgb(0 0 0 / 45%);
--glow-soft: 0 0 0 1px color-mix(in srgb, var(--clinical-accent) 32%, transparent), 0 6px 16px rgb(0 0 0 / 42%);
--overlay-backdrop: rgb(0 0 0 / 72%);

--ring-hairline: 0 0 0 1px rgb(255 255 255 / 9%);
--e0: none;
--e1: inset 0 1px 0 rgb(255 255 255 / 4%), 0 1px 2px rgb(0 0 0 / 40%);
Expand Down
4 changes: 3 additions & 1 deletion src/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ export function Tabs({ items, value, onChange, label, variant = "tabs", classNam
role="tab"
id={`${baseId}-tab-${item.id}`}
aria-selected={selected}
aria-controls={`${baseId}-panel-${item.id}`}
// Only the selected tab points at the live panel. Unselected
// tabs must not aria-controls missing IDs (APG / a11y tree).
aria-controls={children && selected ? `${baseId}-panel-${item.id}` : undefined}
tabIndex={selected ? 0 : -1}
disabled={item.disabled}
onClick={() => onChange(item.id)}
Expand Down
20 changes: 14 additions & 6 deletions src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,23 @@ export function Tooltip({ children, content, placement = "top", className }: Too

if (!isValidElement(children)) return <>{children}</>;

const childProps = children.props as Record<string, unknown>;
const compose =
<E,>(ours: (event: E) => void, theirs?: unknown) =>
(event: E) => {
ours(event);
if (typeof theirs === "function") (theirs as (event: E) => void)(event);
};

const trigger = cloneElement(children, {
"aria-describedby": open ? id : undefined,
onMouseEnter: () => setOpen(true),
onMouseLeave: () => setOpen(false),
onFocus: () => setOpen(true),
onBlur: () => setOpen(false),
onKeyDown: (event: React.KeyboardEvent) => {
onMouseEnter: compose(() => setOpen(true), childProps.onMouseEnter),
onMouseLeave: compose(() => setOpen(false), childProps.onMouseLeave),
onFocus: compose(() => setOpen(true), childProps.onFocus),
onBlur: compose(() => setOpen(false), childProps.onBlur),
onKeyDown: compose((event: React.KeyboardEvent) => {
if (event.key === "Escape") setOpen(false);
},
}, childProps.onKeyDown),
});
Comment thread
cursor[bot] marked this conversation as resolved.

return (
Expand Down
27 changes: 16 additions & 11 deletions tests/ckb-v2-token-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ function block(selector: string) {
// every top-level block whose selector line starts with the given selector.
const opener = `\n${selector} {`;
const grouped = `\n${selector},`;
const found: string[] = [];
let start = stylesheet.indexOf(opener);
if (start === -1) start = stylesheet.indexOf(grouped);
expect(start, `${selector} block is missing from ckb-v2-tokens.css`).toBeGreaterThan(-1);
let combined = "";
while (start > -1) {
const end = stylesheet.indexOf("\n}", start);
found.push(stylesheet.slice(start, end));
combined += stylesheet.slice(start, end);
start = stylesheet.indexOf(opener, end);
}
expect(found.length, `${selector} block is missing from ckb-v2-tokens.css`).toBeGreaterThan(0);
return found;
return combined;
}

function declarations(source: string) {
Expand All @@ -47,13 +47,9 @@ function declarations(source: string) {
return map;
}

const ckbBlocks = block(".ckb-v2");
expect(ckbBlocks.length, "expected distinct structural and light .ckb-v2 blocks").toBe(2);
const structural = declarations(ckbBlocks[0]);
// Parsed separately so a light-only token accidentally declared structural (or
// vice versa) fails here instead of passing through a merged map.
const lightShell = declarations(ckbBlocks[1]);
const darkShell = declarations(block(".dark .ckb-v2").join("\n"));
const structural = declarations(block(".ckb-v2"));
const lightShell = structural; // structural + light now share the .ckb-v2 selector
const darkShell = declarations(block(".dark .ckb-v2"));

function hexOf(tokens: Map<string, string>, name: string) {
const value = tokens.get(name);
Expand Down Expand Up @@ -160,6 +156,15 @@ describe("ckb-v2 ink tiers (#20)", () => {
it("keeps muted text readable on the dark shell too", () => {
expect(contrastRatio(hexOf(darkShell, "--text-muted"), hexOf(darkShell, "--surface"))).toBeGreaterThanOrEqual(4.5);
});

it("re-declares body and heading ink in dark — the cascade port makes the light block match dark subtrees", () => {
// Regression guard for the fall-through break: without these declarations,
// dark subtrees resolve --text to the LIGHT ink and text disappears.
expect(contrastRatio(hexOf(darkShell, "--text"), hexOf(darkShell, "--surface"))).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(hexOf(darkShell, "--text-heading"), hexOf(darkShell, "--surface"))).toBeGreaterThanOrEqual(
4.5,
);
});
});

describe("ckb-v2 command colour (#1, #12)", () => {
Expand Down
29 changes: 29 additions & 0 deletions tests/ui-v2-components.dom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ describe("Tabs", () => {
expect(panel).toHaveAttribute("aria-labelledby", tab.id);
expect(tab).toHaveAttribute("aria-controls", panel.id);
});

it("does not point unselected tabs at missing panel ids", () => {
render(<Harness />);
const selected = screen.getByRole("tab", { name: "Answer" });
const other = screen.getByRole("tab", { name: "Sources" });
expect(selected).toHaveAttribute("aria-controls");
expect(other).not.toHaveAttribute("aria-controls");
});
});

describe("Tooltip", () => {
Expand All @@ -138,6 +146,27 @@ describe("Tooltip", () => {
const tooltip = await screen.findByRole("tooltip");
expect(trigger.getAttribute("aria-describedby")).toBe(tooltip.id);
});

it("composes over existing child event handlers instead of replacing them", async () => {
const onFocus = vi.fn();
const onKeyDown = vi.fn();
render(
<Tooltip content="Extra detail">
<button type="button" onFocus={onFocus} onKeyDown={onKeyDown}>
Trigger
</button>
</Tooltip>,
);

const trigger = screen.getByRole("button");
trigger.focus();
await screen.findByRole("tooltip");
expect(onFocus).toHaveBeenCalled();

await userEvent.keyboard("{Escape}");
expect(onKeyDown).toHaveBeenCalled();
expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
});
});

describe("Pagination", () => {
Expand Down
Loading