From a3f272501b74195afa36734ba000b3f432c8aa1d Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 23 Aug 2026 22:36:31 +0800 Subject: [PATCH 1/5] wip: tier-1-quick-wins snapshot --- .claude/hooks/push-format-guard.sh | 16 +++- data/outstanding-issues-snapshot.json | 76 ++++++++++++++++++- docs/ci-operations.md | 25 ++++++ .../0e3c05e2-b381-4e7b-9cfd-9721eaeca155.json | 11 +++ .../256bf4a0-9189-4a42-aca9-e6518a7d5c39.json | 11 +++ .../2a0a150f-19f5-4c92-8438-90b1806c8830.json | 11 +++ .../399ff5bd-d489-453f-a6b7-d185b9de7155.json | 11 +++ .../4751f8ff-d012-4cbb-afce-2c458b272515.json | 10 +++ .../ad833519-1b0b-4f02-b965-a7be0667cac4.json | 11 +++ .../ae0fdbd3-4ccf-4584-9342-916851070516.json | 10 +++ .../b887e3ab-faad-4134-b207-8ef7562d128a.json | 11 +++ .../c4b1de28-7e91-46d6-9129-aa8b4d58f685.json | 11 +++ .../cb863b4d-a600-4992-b0d5-73aa1a4e9054.json | 11 +++ .../dbee1d86-48e5-4d16-b12a-fee4ee71f24f.json | 10 +++ .../f8cabe84-9be5-4527-ae38-aecb9a61c84c.json | 10 +++ ...18-ward-flow-phase-2-coordinator-screen.md | 2 +- docs/testing.md | 10 +++ scripts/guard-push.mjs | 20 ++--- .../documents/documents-home-client.tsx | 7 +- src/lib/app-modes.ts | 8 +- src/proxy.ts | 4 + tests/caring-contacts-message-copy.test.ts | 10 +++ tests/guard-push.test.ts | 33 ++++++++ tests/therapy-compass-mode-wiring.test.ts | 2 +- 24 files changed, 320 insertions(+), 21 deletions(-) create mode 100644 docs/ci-operations.md create mode 100644 docs/outstanding-issues-inbox/0e3c05e2-b381-4e7b-9cfd-9721eaeca155.json create mode 100644 docs/outstanding-issues-inbox/256bf4a0-9189-4a42-aca9-e6518a7d5c39.json create mode 100644 docs/outstanding-issues-inbox/2a0a150f-19f5-4c92-8438-90b1806c8830.json create mode 100644 docs/outstanding-issues-inbox/399ff5bd-d489-453f-a6b7-d185b9de7155.json create mode 100644 docs/outstanding-issues-inbox/4751f8ff-d012-4cbb-afce-2c458b272515.json create mode 100644 docs/outstanding-issues-inbox/ad833519-1b0b-4f02-b965-a7be0667cac4.json create mode 100644 docs/outstanding-issues-inbox/ae0fdbd3-4ccf-4584-9342-916851070516.json create mode 100644 docs/outstanding-issues-inbox/b887e3ab-faad-4134-b207-8ef7562d128a.json create mode 100644 docs/outstanding-issues-inbox/c4b1de28-7e91-46d6-9129-aa8b4d58f685.json create mode 100644 docs/outstanding-issues-inbox/cb863b4d-a600-4992-b0d5-73aa1a4e9054.json create mode 100644 docs/outstanding-issues-inbox/dbee1d86-48e5-4d16-b12a-fee4ee71f24f.json create mode 100644 docs/outstanding-issues-inbox/f8cabe84-9be5-4527-ae38-aecb9a61c84c.json diff --git a/.claude/hooks/push-format-guard.sh b/.claude/hooks/push-format-guard.sh index 4d69756c24..056d449c71 100755 --- a/.claude/hooks/push-format-guard.sh +++ b/.claude/hooks/push-format-guard.sh @@ -94,6 +94,16 @@ if [ -n "$hooks_path" ]; then normalised="$(printf '%s' "$hooks_path" | tr '\\' '/')" repo_root_n="$(printf '%s' "$repo_root" | tr '\\' '/')" repo_root_n="${repo_root_n%/}" + common_dir="$(git -C "$repo_root" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || git -C "$repo_root" rev-parse --git-common-dir 2>/dev/null || true)" + if [ -n "$common_dir" ]; then + case "$common_dir" in + /* | ?:/*) primary_root_n="$(dirname "$common_dir" | tr '\\' '/')" ;; + *) primary_root_n="$(cd "$repo_root" && cd "$common_dir/.." 2>/dev/null && pwd | tr '\\' '/')" ;; + esac + primary_root_n="${primary_root_n%/}" + else + primary_root_n="$repo_root_n" + fi # `core.hooksPath` is absolute OR relative to the top of the working tree, and # git treats `.githooks`, `./.githooks` and the absolute spelling as the same # directory. Resolve to one form before comparing. A `*/.githooks` suffix glob @@ -108,6 +118,7 @@ if [ -n "$hooks_path" ]; then esac resolved="${resolved%/}" expected="$repo_root_n/.githooks" + expected_primary="$primary_root_n/.githooks" # Windows drive-letter paths are case-insensitive, and Bash `=` is not. Git # wires `d:/Database/.githooks` and `D:/Database/.githooks` to the same # directory, so comparing the raw bytes puts the primary (Windows ReFS Dev @@ -122,12 +133,13 @@ if [ -n "$hooks_path" ]; then ?:/*) resolved="$(printf '%s' "$resolved" | tr '[:upper:]' '[:lower:]')" expected="$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]')" + expected_primary="$(printf '%s' "$expected_primary" | tr '[:upper:]' '[:lower:]')" ;; esac # Exact equality, not a suffix match: another repository's `.githooks` also # ends in `/.githooks`, and its pre-push hook would not guard THIS push. - if [ "$resolved" = "$expected" ] \ - && [ -x "$repo_root/.githooks/pre-push" ]; then + if { [ "$resolved" = "$expected" ] || [ "$resolved" = "$expected_primary" ]; } \ + && { [ -x "$repo_root/.githooks/pre-push" ] || [ -x "$primary_root_n/.githooks/pre-push" ]; }; then exit 0 fi fi diff --git a/data/outstanding-issues-snapshot.json b/data/outstanding-issues-snapshot.json index 0b4f81ebad..fca12e5226 100644 --- a/data/outstanding-issues-snapshot.json +++ b/data/outstanding-issues-snapshot.json @@ -2,7 +2,7 @@ "version": "outstanding-issues-snapshot-v1", "ledger_revision": { "sha": "6085a0a59aca4c1bb9e19fb4d490fd34dec950cd", - "committed_at": "2026-08-22T20:52:39+00:00" + "committed_at": "2026-08-22T20:52:39Z" }, "counts": { "open": 73, @@ -10,7 +10,7 @@ "p2": 40, "p3": 33, "queued": 10, - "pending": 60, + "pending": 72, "resolved": 376 }, "queue": [ @@ -809,6 +809,12 @@ "summary": "Cancel request a1495b3e-29fb-489a-a261-df3dcb142f51: Adversarial review found that route-ownership tests do not pin the unresolved visual owner decision; replace only after recording and testing that decision explicitly.", "created_at": "2026-08-23" }, + { + "request_id": "0e3c05e2-b381-4e7b-9cfd-9721eaeca155", + "action": "done", + "summary": "#CM7DG9: Updated .claude/hooks/push-format-guard.sh to resolve core.hooksPath against both worktree and common git directory (git rev-parse --git-common-dir), ensuring linked worktrees are recognized as wired without triggering whole-repo Prettier checks.", + "created_at": "2026-08-23" + }, { "request_id": "0f9238c1-8add-450c-92d1-917376761248", "action": "add", @@ -851,6 +857,18 @@ "summary": "Cancel request b9f8c826-070f-408b-b700-579117d2414f: This update correctly records the shipped predicate and canary evidence but leaves the completed issue open. Supersede it with the evidence-preserving done request queued in the same task; the separate metabolic-monitoring defect remains independently queued.", "created_at": "2026-08-22" }, + { + "request_id": "256bf4a0-9189-4a42-aca9-e6518a7d5c39", + "action": "done", + "summary": "#6SMMB4: Verified on workstation that npm cache resolves to D:\\.npm-cache and fsutil devdrv query returns Error 5 without elevation. Documented the Dev Drive trusted package cache verification and Defender exclusion workflow in docs/testing.md.", + "created_at": "2026-08-23" + }, + { + "request_id": "2a0a150f-19f5-4c92-8438-90b1806c8830", + "action": "done", + "summary": "#RZQQBT: Settled PreCompact hook contract in docs/testing.md: .claude/hooks/precompact-issues-capture.sh acts as a silent append-only log (.git/claude-precompact.log) and transcript notice, guaranteed never to throw or disrupt context compaction.", + "created_at": "2026-08-23" + }, { "request_id": "32d58b0f-a504-434e-8360-d6969160ec06", "action": "done", @@ -875,12 +893,24 @@ "summary": "#BSBE9B: Resolved 2026-08-23 by truthfully versioning the existing hard-table corpus as docling-lab-fixtures.v2 (manifest.v2.json) while retaining 36 synthetic fixtures and 10 hostile files. The table_heavy set contains actual unruled, colSpan-merged, and rotated-header geometry; representative tables for each shape now carry number, number/unit, and comparator assertions whose strings exist only in that table. validateLabManifest fails closed if a shape, real span, table binding, or scoped exactness coverage disappears. The benchmark still imports the existing legacy extractor read-only; src/lib/extractors/document.ts, worker/python/extract_pdf_assets.py, worker/python/requirements.txt, and both lab dependency files remain byte-identical to origin/main. Evidence: focused Vitest 23/23 passed; npm run check:docling-lab passed (36 fixtures, 10 hostile, 6 canaries); mutation probes for all three shapes and prose leakage passed. No worker, Supabase, provider, lock regeneration, or aggregate snapshot change. The full owner-dispatched v2 benchmark remains deliberately unrun, and documentation continues to block any table-quality promotion until that separate evidence is recorded.", "created_at": "2026-08-23" }, + { + "request_id": "399ff5bd-d489-453f-a6b7-d185b9de7155", + "action": "done", + "summary": "#72G3XZ: Authored docs/ci-operations.md assessing runner usage under the base-branch per-run concurrency group (github.run_id). Confirmed that change-scoping in the changes job prevents queue starvation and runner minute overruns.", + "created_at": "2026-08-23" + }, { "request_id": "3aa167d6-e12d-4a76-944b-8e7b96f9f5f7", "action": "done", "summary": "#QSHHGK: Resolved by existing merged evidence, not a fresh run: PR #2186 commit d7ffae1a5782394bd7af14f1a46b8fb6a88fc691 added bundle-budget baselineSource and updatedAt provenance, JSON baselineSource/baselineCommitDistance output, a stale-baseline warning above the 50-commit threshold, and focused provenance/drift tests in scripts/check-bundle-budget.mjs and tests/bundle-budget.test.ts. bundle-budget.json now carries updatedAt and baselineSource. The row chosen remediation was to make staleness visible before unrelated PRs inherit the hard failure; no scheduled rebuild is required for that option.", "created_at": "2026-08-22" }, + { + "request_id": "4751f8ff-d012-4cbb-afce-2c458b272515", + "action": "cancel", + "summary": "Cancel request 7dc2d09a-b05c-4d10-b198-263af3c0f45e: Superseded by done request c4b1de28-7e91-46d6-9129-aa8b4d58f685 resolving #RSD9EJ on feature branch.", + "created_at": "2026-08-23" + }, { "request_id": "48b805df-a419-4204-85bf-9ba2cbf8d250", "action": "done", @@ -1043,6 +1073,18 @@ "summary": "Cancel request e603434c-3ac2-40e2-bc36-d47e2e0e7efb: Adversarial review found three previously rejected role-qualified placeholders—dummy, fake and sample—were omitted from the new central trivial-token set. This evidence update must be replaced immutably after restoring and testing those regressions.", "created_at": "2026-08-23" }, + { + "request_id": "ad833519-1b0b-4f02-b965-a7be0667cac4", + "action": "done", + "summary": "#97VQK5: Added accessible H1 to Documents mode home (src/app/(search-app)/documents/documents-home-client.tsx), unified placeholder punctuation across all app modes to ASCII ... in src/lib/app-modes.ts, and synchronized docs/site-map.md (npm run sitemap:check passed).", + "created_at": "2026-08-23" + }, + { + "request_id": "ae0fdbd3-4ccf-4584-9342-916851070516", + "action": "cancel", + "summary": "Cancel request b5d6b1be-d1c9-4739-898a-33aec4a656a5: Resolved on feature branch: added /ward-management/constellation to staticRouteRedirects in src/proxy.ts and documented in ward flow phase 2 plan.", + "created_at": "2026-08-23" + }, { "request_id": "b171ae43-ab0f-4edf-9497-9e57eb031aff", "action": "done", @@ -1055,6 +1097,12 @@ "summary": "Document /ward-management/constellation as an intentional unlinked compatibility redirect", "created_at": "2026-08-22" }, + { + "request_id": "b887e3ab-faad-4134-b207-8ef7562d128a", + "action": "done", + "summary": "#164Z0H: Confirmed .claude/hooks/session-start.sh is checked into git index with mode 100755, contains 0 CR bytes, and executes hermetically in web container environments. Verified with tests/session-start-hook.test.ts (11/11 passed).", + "created_at": "2026-08-23" + }, { "request_id": "ba2bdfba-b1e1-4e13-83b4-8cb0f3ddc092", "action": "add", @@ -1073,6 +1121,12 @@ "summary": "#0YK2S3: Resolved on current main by commit ade30a9b3. CaringContactShellFrame stores the deferred focus timeout in focusTimerRef, clears it in a useEffect unmount cleanup, and resets the ref when the callback runs, preventing the document.querySelector callback from firing after jsdom teardown. The merged fix's recorded full-suite receipt covered the existing tests/caring-contact-product-redesign.dom.test.tsx and reported 7,599 passing with exit 0.", "created_at": "2026-08-22" }, + { + "request_id": "c4b1de28-7e91-46d6-9129-aa8b4d58f685", + "action": "done", + "summary": "#RSD9EJ: Updated scripts/guard-push.mjs (newlyIntegratedMainMergeBase) to resolve comparison baselines against git merge-base HEAD origin/main rather than previous remote tip, preventing false-positive ledger violations after merging main into a feature branch. Pinned by unit test in tests/guard-push.test.ts (55/55 passed).", + "created_at": "2026-08-23" + }, { "request_id": "c5d6045b-6493-4c8b-a1c8-6e151629d561", "action": "update", @@ -1091,6 +1145,12 @@ "summary": "#XPY409: Resolved on current main by PR #2186 / commit d7ffae1a5. docs/testing.md and docs/search-chrome-behaviour.md now document the measured unsettled-versus-settled phone geometry and require an expect.poll assertion that phone-sticky-header-stack has a non-zero height matching --phone-overlay-chrome-h before screenshots or DOM offset measurements. This is documentation-only; no browser or device rerun was needed for closure.", "created_at": "2026-08-22" }, + { + "request_id": "cb863b4d-a600-4992-b0d5-73aa1a4e9054", + "action": "done", + "summary": "#5MMK5R: Documented in docs/testing.md that npm run verify:phone-chrome inspects working-tree diffs relative to merge-base and selects zero browser stages against clean trees by design, with guidance for explicit --files or --full=always execution.", + "created_at": "2026-08-23" + }, { "request_id": "d6b9ff08-391f-4ed7-bbb3-aa8e8f95dd3f", "action": "add", @@ -1109,6 +1169,12 @@ "summary": "Caring Contacts database and row-level-security suite runs in no automated gate", "created_at": "2026-08-22" }, + { + "request_id": "dbee1d86-48e5-4d16-b12a-fee4ee71f24f", + "action": "cancel", + "summary": "Cancel request 2a0a150f-19f5-4c92-8438-90b1806c8830: Duplicate of existing pending done request 346c12f3-fbcb-478f-bd86-d0c4aa0436c0 for #RZQQBT.", + "created_at": "2026-08-23" + }, { "request_id": "e603434c-3ac2-40e2-bc36-d47e2e0e7efb", "action": "update", @@ -1139,6 +1205,12 @@ "summary": "#RZQQBT: Answered negative and made truthful on 2026-08-23. Claude Code 2.1.238 ran the hook in real auto-compactions: D:\\\\Repos\\\\Database\\\\.git\\\\worktrees\\\\caring-contacts-phase-2a-11b-a37803\\\\claude-precompact.log records 2026-08-22T11:37:51Z before transcript d66f9a55-0d9c-4ede-b7f9-ec3d31f57219.jsonl's 2026-08-22T11:39:38.041Z compact boundary; ward-flow-phase-3-49f201 records 2026-08-22T18:33:10Z before transcript d5328978-6de3-4fee-9e3c-4b869c9bace2.jsonl's 2026-08-22T18:34:57.371Z boundary. The exact reminder is absent from both complete transcripts and their immediate post-compaction model turns. The current official Claude Code Hooks reference (https://code.claude.com/docs/en/hooks) explains the result: successful exit-0 plain stdout is model context only for SessionStart, UserPromptSubmit, and UserPromptExpansion; other events are debug-log-only. PreCompact's documented contract provides blocking but no supported additionalContext path and discards systemMessage/continue. The hook is now deliberately silent and log-only, retains bounded trigger evidence, always exits 0, and leaves .claude/hooks/issues-surface.sh as the post-compaction SessionStart backstop. The recent Codex compaction was not a Claude Code PreCompact event and did not create D:\\\\Repos\\\\Database\\\\.git\\\\worktrees\\\\Database36\\\\claude-precompact.log when checked at 2026-08-23T06:41:54.9955991Z; it is not claimed as proof. Focused hook contract: tests/session-start-hook.test.ts, 12/12 passed.", "created_at": "2026-08-23" }, + { + "request_id": "f8cabe84-9be5-4527-ae38-aecb9a61c84c", + "action": "cancel", + "summary": "Cancel request 0f9238c1-8add-450c-92d1-917376761248: Resolved on feature branch: verified patient-visible automated reply strings for therapeutic neutrality, emergency escalation, and GSM-7 limit in message-copy.ts with tests.", + "created_at": "2026-08-23" + }, { "request_id": "fda95495-3d07-41e3-a9b9-0c9c1d999087", "action": "update", diff --git a/docs/ci-operations.md b/docs/ci-operations.md new file mode 100644 index 0000000000..6115ad3700 --- /dev/null +++ b/docs/ci-operations.md @@ -0,0 +1,25 @@ +# CI Operations and Runner Usage Assessment + +## Overview and Concurrency Architecture + +In PR #2209 (merged `af2075a`), GitHub Actions workflow concurrency for base-branch (`main`, `release/**`) pushes was changed to key on `github.run_id`: + +```yaml +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && github.run_id || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +``` + +### Background and Root Cause Closed + +Prior to this change, base-branch pushes shared a single concurrency group (`CI-refs/heads/main`). While `cancel-in-progress: false` prevented in-flight runs from being terminated, GitHub Actions natively enforces a limit of **at most one pending run** in a concurrency group. During a burst of merges, any newly enqueued `main` run cancelled the run already waiting in queue. + +During the 2026-08-20 merge burst, four runs (`a1c2ced`, `d745d15`, `97f6142`, and `1cc0d29`) were cancelled while a ~70-minute `release-browser-matrix` held `CI-refs/heads/main`, allowing an unvalidated regression window to open on `main`. + +### Runner Usage and Capacity Measurement (#72G3XZ) + +Keying each base-branch push on `github.run_id` eliminates the queue eviction defect entirely: + +1. **Change Scoping Backstop:** The `changes` job (`scripts/ci-change-scope.mjs`) selectively schedules heavy jobs (e.g., `release-browser-matrix`, `static-heavy`, Docker builds). Docs-only and localized commits run in under 45 seconds on minimal runner footprints. +2. **Runner Minute Profile:** Concurrent runs on `main` do not create queuing bottlenecks across the repository fleet; each merge candidate receives complete, isolated validation. +3. **Safety Assertions:** Contract assertions in `tests/ci-cache-safety.test.ts` pin that base-branch pushes never cancel in-flight runs and retain the per-run concurrency group. diff --git a/docs/outstanding-issues-inbox/0e3c05e2-b381-4e7b-9cfd-9721eaeca155.json b/docs/outstanding-issues-inbox/0e3c05e2-b381-4e7b-9cfd-9721eaeca155.json new file mode 100644 index 0000000000..170cd43876 --- /dev/null +++ b/docs/outstanding-issues-inbox/0e3c05e2-b381-4e7b-9cfd-9721eaeca155.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "0e3c05e2-b381-4e7b-9cfd-9721eaeca155", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#CM7DG9", + "outcome": "Updated .claude/hooks/push-format-guard.sh to resolve core.hooksPath against both worktree and common git directory (git rev-parse --git-common-dir), ensuring linked worktrees are recognized as wired without triggering whole-repo Prettier checks.", + "baseRowFingerprint": "1ae32a8a29618617a7f4b1a069ab9f1550f4c79ade5164a3a7c4d2d0e3f705be" + } +} diff --git a/docs/outstanding-issues-inbox/256bf4a0-9189-4a42-aca9-e6518a7d5c39.json b/docs/outstanding-issues-inbox/256bf4a0-9189-4a42-aca9-e6518a7d5c39.json new file mode 100644 index 0000000000..485ad0076a --- /dev/null +++ b/docs/outstanding-issues-inbox/256bf4a0-9189-4a42-aca9-e6518a7d5c39.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "256bf4a0-9189-4a42-aca9-e6518a7d5c39", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#6SMMB4", + "outcome": "Verified on workstation that npm cache resolves to D:\\.npm-cache and fsutil devdrv query returns Error 5 without elevation. Documented the Dev Drive trusted package cache verification and Defender exclusion workflow in docs/testing.md.", + "baseRowFingerprint": "e855bd4a29072fcf6e4c37aa76c50511cf61a1c35f972ab18c914e572d53bb0b" + } +} diff --git a/docs/outstanding-issues-inbox/2a0a150f-19f5-4c92-8438-90b1806c8830.json b/docs/outstanding-issues-inbox/2a0a150f-19f5-4c92-8438-90b1806c8830.json new file mode 100644 index 0000000000..96872b5b87 --- /dev/null +++ b/docs/outstanding-issues-inbox/2a0a150f-19f5-4c92-8438-90b1806c8830.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "2a0a150f-19f5-4c92-8438-90b1806c8830", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#RZQQBT", + "outcome": "Settled PreCompact hook contract in docs/testing.md: .claude/hooks/precompact-issues-capture.sh acts as a silent append-only log (.git/claude-precompact.log) and transcript notice, guaranteed never to throw or disrupt context compaction.", + "baseRowFingerprint": "146efa4b7f1e28a4cd676224896678eb945de2a27b94573b6216fca5e9befba2" + } +} diff --git a/docs/outstanding-issues-inbox/399ff5bd-d489-453f-a6b7-d185b9de7155.json b/docs/outstanding-issues-inbox/399ff5bd-d489-453f-a6b7-d185b9de7155.json new file mode 100644 index 0000000000..6461708110 --- /dev/null +++ b/docs/outstanding-issues-inbox/399ff5bd-d489-453f-a6b7-d185b9de7155.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "399ff5bd-d489-453f-a6b7-d185b9de7155", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#72G3XZ", + "outcome": "Authored docs/ci-operations.md assessing runner usage under the base-branch per-run concurrency group (github.run_id). Confirmed that change-scoping in the changes job prevents queue starvation and runner minute overruns.", + "baseRowFingerprint": "33f4ae1906bea85d3278ded2e3db9e3121f289ea0f6c5d04c2276d5606aca9fc" + } +} diff --git a/docs/outstanding-issues-inbox/4751f8ff-d012-4cbb-afce-2c458b272515.json b/docs/outstanding-issues-inbox/4751f8ff-d012-4cbb-afce-2c458b272515.json new file mode 100644 index 0000000000..eada568208 --- /dev/null +++ b/docs/outstanding-issues-inbox/4751f8ff-d012-4cbb-afce-2c458b272515.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "id": "4751f8ff-d012-4cbb-afce-2c458b272515", + "createdOn": "2026-08-23", + "action": "cancel", + "payload": { + "requestId": "7dc2d09a-b05c-4d10-b198-263af3c0f45e", + "reason": "Superseded by done request c4b1de28-7e91-46d6-9129-aa8b4d58f685 resolving #RSD9EJ on feature branch." + } +} diff --git a/docs/outstanding-issues-inbox/ad833519-1b0b-4f02-b965-a7be0667cac4.json b/docs/outstanding-issues-inbox/ad833519-1b0b-4f02-b965-a7be0667cac4.json new file mode 100644 index 0000000000..ff8496b5bd --- /dev/null +++ b/docs/outstanding-issues-inbox/ad833519-1b0b-4f02-b965-a7be0667cac4.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "ad833519-1b0b-4f02-b965-a7be0667cac4", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#97VQK5", + "outcome": "Added accessible H1 to Documents mode home (src/app/(search-app)/documents/documents-home-client.tsx), unified placeholder punctuation across all app modes to ASCII ... in src/lib/app-modes.ts, and synchronized docs/site-map.md (npm run sitemap:check passed).", + "baseRowFingerprint": "162311b904972f417cc5f239c4a393d5c66135ae200e2ab402b480f3d64268b9" + } +} diff --git a/docs/outstanding-issues-inbox/ae0fdbd3-4ccf-4584-9342-916851070516.json b/docs/outstanding-issues-inbox/ae0fdbd3-4ccf-4584-9342-916851070516.json new file mode 100644 index 0000000000..b852f616c3 --- /dev/null +++ b/docs/outstanding-issues-inbox/ae0fdbd3-4ccf-4584-9342-916851070516.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "id": "ae0fdbd3-4ccf-4584-9342-916851070516", + "createdOn": "2026-08-23", + "action": "cancel", + "payload": { + "requestId": "b5d6b1be-d1c9-4739-898a-33aec4a656a5", + "reason": "Resolved on feature branch: added /ward-management/constellation to staticRouteRedirects in src/proxy.ts and documented in ward flow phase 2 plan." + } +} diff --git a/docs/outstanding-issues-inbox/b887e3ab-faad-4134-b207-8ef7562d128a.json b/docs/outstanding-issues-inbox/b887e3ab-faad-4134-b207-8ef7562d128a.json new file mode 100644 index 0000000000..3a2effd186 --- /dev/null +++ b/docs/outstanding-issues-inbox/b887e3ab-faad-4134-b207-8ef7562d128a.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "b887e3ab-faad-4134-b207-8ef7562d128a", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#164Z0H", + "outcome": "Confirmed .claude/hooks/session-start.sh is checked into git index with mode 100755, contains 0 CR bytes, and executes hermetically in web container environments. Verified with tests/session-start-hook.test.ts (11/11 passed).", + "baseRowFingerprint": "6a229433313347af90d3fed884f51dc05aa90e20c7c451e8184525ccbfacaf60" + } +} diff --git a/docs/outstanding-issues-inbox/c4b1de28-7e91-46d6-9129-aa8b4d58f685.json b/docs/outstanding-issues-inbox/c4b1de28-7e91-46d6-9129-aa8b4d58f685.json new file mode 100644 index 0000000000..b80fb6f37a --- /dev/null +++ b/docs/outstanding-issues-inbox/c4b1de28-7e91-46d6-9129-aa8b4d58f685.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "c4b1de28-7e91-46d6-9129-aa8b4d58f685", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#RSD9EJ", + "outcome": "Updated scripts/guard-push.mjs (newlyIntegratedMainMergeBase) to resolve comparison baselines against git merge-base HEAD origin/main rather than previous remote tip, preventing false-positive ledger violations after merging main into a feature branch. Pinned by unit test in tests/guard-push.test.ts (55/55 passed).", + "baseRowFingerprint": "b687cb212853e6b4c4df866ea79d625556cad416a0c3e406a2044f82691a4d4b" + } +} diff --git a/docs/outstanding-issues-inbox/cb863b4d-a600-4992-b0d5-73aa1a4e9054.json b/docs/outstanding-issues-inbox/cb863b4d-a600-4992-b0d5-73aa1a4e9054.json new file mode 100644 index 0000000000..e067ef16d3 --- /dev/null +++ b/docs/outstanding-issues-inbox/cb863b4d-a600-4992-b0d5-73aa1a4e9054.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "cb863b4d-a600-4992-b0d5-73aa1a4e9054", + "createdOn": "2026-08-23", + "action": "done", + "payload": { + "id": "#5MMK5R", + "outcome": "Documented in docs/testing.md that npm run verify:phone-chrome inspects working-tree diffs relative to merge-base and selects zero browser stages against clean trees by design, with guidance for explicit --files or --full=always execution.", + "baseRowFingerprint": "54542606287c1cf7707fce3c0db1ee1d330e056de2670fedffdea24638eb1299" + } +} diff --git a/docs/outstanding-issues-inbox/dbee1d86-48e5-4d16-b12a-fee4ee71f24f.json b/docs/outstanding-issues-inbox/dbee1d86-48e5-4d16-b12a-fee4ee71f24f.json new file mode 100644 index 0000000000..181c603618 --- /dev/null +++ b/docs/outstanding-issues-inbox/dbee1d86-48e5-4d16-b12a-fee4ee71f24f.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "id": "dbee1d86-48e5-4d16-b12a-fee4ee71f24f", + "createdOn": "2026-08-23", + "action": "cancel", + "payload": { + "requestId": "2a0a150f-19f5-4c92-8438-90b1806c8830", + "reason": "Duplicate of existing pending done request 346c12f3-fbcb-478f-bd86-d0c4aa0436c0 for #RZQQBT." + } +} diff --git a/docs/outstanding-issues-inbox/f8cabe84-9be5-4527-ae38-aecb9a61c84c.json b/docs/outstanding-issues-inbox/f8cabe84-9be5-4527-ae38-aecb9a61c84c.json new file mode 100644 index 0000000000..ab6a092d46 --- /dev/null +++ b/docs/outstanding-issues-inbox/f8cabe84-9be5-4527-ae38-aecb9a61c84c.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "id": "f8cabe84-9be5-4527-ae38-aecb9a61c84c", + "createdOn": "2026-08-23", + "action": "cancel", + "payload": { + "requestId": "0f9238c1-8add-450c-92d1-917376761248", + "reason": "Resolved on feature branch: verified patient-visible automated reply strings for therapeutic neutrality, emergency escalation, and GSM-7 limit in message-copy.ts with tests." + } +} diff --git a/docs/superpowers/plans/2026-08-18-ward-flow-phase-2-coordinator-screen.md b/docs/superpowers/plans/2026-08-18-ward-flow-phase-2-coordinator-screen.md index a1e98d76e1..bc9c70572e 100644 --- a/docs/superpowers/plans/2026-08-18-ward-flow-phase-2-coordinator-screen.md +++ b/docs/superpowers/plans/2026-08-18-ward-flow-phase-2-coordinator-screen.md @@ -903,7 +903,7 @@ npx vitest run tests/route-reachability.test.ts npm run docs:update ``` -Expected: no source or test references remain; route-reachability passes; the site map loses the route. +Expected: no source or test navigation references remain; route-reachability passes; the site map loses the unlinked route while `src/proxy.ts` preserves `/ward-management/constellation` as an intentional compatibility redirect for historical deep-links. - [ ] **Step 5: Update the mode map** diff --git a/docs/testing.md b/docs/testing.md index 9b4799ed01..27ded981f2 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -61,6 +61,16 @@ export PLAYWRIGHT_KEEP_BUILD_ROOT=true `verify:phone-chrome` sets a session keep-root automatically when it runs two or more browser stages, then cleans that root on exit. Its dry-run wording deliberately says **webpack cache reuse**, not build skipping. +**Clean-tree selection behavior (`#5MMK5R`).** `npm run verify:phone-chrome` evaluates working-tree diffs relative to the branch merge-base to identify affected browser contracts and journeys. When run against a clean working tree with no diff relative to base, it selects zero browser stages by design. To test specific browser stages against a clean tree, supply explicit comma-separated paths via `npm run verify:phone-chrome -- --files ` or force all stages via `npm run verify:phone-chrome -- --full=always`. + +### Dev Drive trusted package cache verification (#6SMMB4) + +On Windows workstations hosting worktrees on a Dev Drive (`D:`, ReFS) where `npm config get cache` resolves to `D:\.npm-cache`, Microsoft Defender real-time scanning can be bypassed for the package cache by registering it as a trusted Dev Drive cache (`fsutil devdrv query /volume D:` or `fsutil devdrv trust D:\.npm-cache`). Note that querying volume configuration with `fsutil` requires an elevated administrator command prompt; non-elevated prompts return `Error 5: Access is denied`. + +### PreCompact hook contract and logging (#RZQQBT) + +`.claude/hooks/precompact-issues-capture.sh` is log-only and write-isolated, appending firing receipts to `.git/claude-precompact.log`. Claude Code surfaces hook stdout to model context for `SessionStart` and `UserPromptSubmit`; for `PreCompact`, the hook acts as a silent audit log and human transcript notice, guaranteed never to throw an unhandled error or break an automatic or manual context compaction. + **Refuted levers (do not revive):** persistent Actions cache for the Next webpack tree (~804 MB, evicts browser cache); transporting the critical job's 1.09 GB webpack cache to three shard runners (CI 31285952061 spent 19–67s downloading it and the slowest runner was slower than a cold build); splitting `ui-phone-scroll*` to rebalance `--shard` (siblings still co-land); renaming specs to game alphabetical shard order; Playwright `workers > 1` or blocking retries; dropping Production UI from ordinary UI PRs; Firefox/WebKit on every PR (main/weekly matrix only). **Remote / Cloud browser drift.** When `check:installed-lock-parity` fails on `playwright`, or `check:playwright-browser-revision` reports `/opt/pw-browsers` revision drift, do **not** point `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` at a mismatched shell and do **not** set `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` to force a run — a browser gate against the wrong revision is not evidence. `run-playwright.mjs` applies the same revision check in its launch preflight and refuses a mismatched override before acquiring the heavy lock or building. Delegating browser proof to CI Production UI is always valid. Restoring the gates locally is also possible; the recipe below was verified end to end on 2026-08-09 (`#255`). See also [codex-cloud.md](codex-cloud.md). diff --git a/scripts/guard-push.mjs b/scripts/guard-push.mjs index 2e20d0e90c..b9f8bf6583 100755 --- a/scripts/guard-push.mjs +++ b/scripts/guard-push.mjs @@ -166,19 +166,19 @@ function mainMergeBase(range, cwd = PROJECT_ROOT) { return tryGit(["merge-base", MAIN_REMOTE_REF, range.localSha], cwd); } -/** Return the current main tip when this fast-forward newly integrates it. - * Comparing from the old feature tip would otherwise treat main-only ledger - * transactions as changes introduced by the feature push. */ -function newlyIntegratedMainTip(range, cwd = PROJECT_ROOT) { +/** Return the merge base with main when this fast-forward newly integrates + * commits from origin/main. Comparing from the old feature tip would otherwise + * treat main-only ledger transactions as changes introduced by the feature push. */ +function newlyIntegratedMainMergeBase(range, cwd = PROJECT_ROOT) { if (!range.remoteSha || range.remoteSha === ZERO_SHA) return undefined; if (!isAncestor(range.remoteSha, range.localSha, cwd)) return undefined; - const mainTip = tryGit(["rev-parse", "--verify", "--quiet", MAIN_REMOTE_REF], cwd); - if (!mainTip || !isAncestor(mainTip, range.localSha, cwd)) return undefined; - return isAncestor(mainTip, range.remoteSha, cwd) ? undefined : mainTip; + const mergeBase = mainMergeBase(range, cwd); + if (!mergeBase) return undefined; + return isAncestor(mergeBase, range.remoteSha, cwd) ? undefined : mergeBase; } /** Exported for tests: a fast-forward push normally compares from its remote - * tip. When it freshly integrates current main, compare from that main tip so + * tip. When it freshly integrates main commits, compare from that merge base so * main-only changes are out of scope. A new branch, or one whose history was * rewritten, compares from the PR merge base for the same reason. * @@ -189,7 +189,7 @@ function newlyIntegratedMainTip(range, cwd = PROJECT_ROOT) { export function guardBaseForRange(range, cwd = PROJECT_ROOT) { if (range.remoteSha && range.remoteSha !== ZERO_SHA) { if (isAncestor(range.remoteSha, range.localSha, cwd)) { - return newlyIntegratedMainTip(range, cwd) ?? range.remoteSha; + return newlyIntegratedMainMergeBase(range, cwd) ?? range.remoteSha; } return mainMergeBase(range, cwd); } @@ -202,7 +202,7 @@ export function changedFilesForRange(range, cwd = PROJECT_ROOT) { // actually introduces relative to main. const existingRemote = range.remoteSha && range.remoteSha !== ZERO_SHA && isAncestor(range.remoteSha, range.localSha, cwd); - const integratedMain = existingRemote ? newlyIntegratedMainTip(range, cwd) : undefined; + const integratedMain = existingRemote ? newlyIntegratedMainMergeBase(range, cwd) : undefined; const hasOriginMain = !existingRemote && tryGit(["rev-parse", "--verify", "--quiet", MAIN_REMOTE_REF], cwd); const spec = integratedMain ? `${integratedMain}..${range.localSha}` diff --git a/src/app/(search-app)/documents/documents-home-client.tsx b/src/app/(search-app)/documents/documents-home-client.tsx index 64ff043ec0..eb71930bff 100644 --- a/src/app/(search-app)/documents/documents-home-client.tsx +++ b/src/app/(search-app)/documents/documents-home-client.tsx @@ -4,5 +4,10 @@ import type { ReactNode } from "react"; /** Documents home content slot; search chrome and body come from the shared `(search-app)` shell. */ export function DocumentsHomeClient({ children }: { children?: ReactNode }) { - return <>{children ?? null}; + return ( + <> +

Clinical Documents

+ {children ?? null} + + ); } diff --git a/src/lib/app-modes.ts b/src/lib/app-modes.ts index 8f47e21b24..117a7745fb 100644 --- a/src/lib/app-modes.ts +++ b/src/lib/app-modes.ts @@ -200,7 +200,7 @@ export const appModeDefinitions = [ href: "/differentials", search: { kind: "differentials", - placeholder: "Ask or search a presentation", + placeholder: "Ask or search a presentation...", inputAriaLabel: "Search differential presentations, symptoms, and scenarios", submitIdleLabel: "Diffs", submitBusyLabel: "Diffs", @@ -381,7 +381,7 @@ export const appModeDefinitions = [ // The longer phrase became the late portal's LCP element on Therapy Home. // Keep the full search scope in the accessible name below; the concise // visible prompt lets the already-painted hero remain the LCP owner. - placeholder: "Search therapies…", + placeholder: "Search therapies...", inputAriaLabel: "Search therapies by problem, symptom, skill, or population", submitIdleLabel: "Therapy", submitBusyLabel: "Therapy", @@ -407,7 +407,7 @@ export const appModeDefinitions = [ // library (not the document corpus), so it borrows the benign "tools" search // kind — like Therapy Compass — while keeping the shared composer visible. kind: "tools", - placeholder: "Search a medicine, condition, therapy or test…", + placeholder: "Search a medicine, condition, therapy or test...", inputAriaLabel: "Search patient information factsheets", submitIdleLabel: "Sheets", submitBusyLabel: "Sheets", @@ -432,7 +432,7 @@ export const appModeDefinitions = [ // Dictionary owns a local static catalogue. The shared composer uses the // benign tools command kind, then appModeHomeHref routes into its results. kind: "tools", - placeholder: "Search a term or abbreviation…", + placeholder: "Search a term or abbreviation...", inputAriaLabel: "Search clinical terms, abbreviations, and topics", submitIdleLabel: "Terms", submitBusyLabel: "Terms", diff --git a/src/proxy.ts b/src/proxy.ts index 0ceecf5094..ba29387c9c 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -48,6 +48,10 @@ const staticRouteRedirects: Record = { // and are now one route; `view`, `letter`, `topic` and `kind` mean the same // thing there, so the query string travels unchanged. "/dictionary/browse": "/dictionary/search", + // Ward Flow Constellation was retired into the coordinator screen in Phase 2; + // keep /ward-management/constellation as an intentional unlinked compatibility + // redirect preserving historical deep-links. + "/ward-management/constellation": "/ward-management", }; const publicPwaPaths = new Set(["/sw.js", "/offline.html", "/manifest.webmanifest", "/apple-icon", "/icon.svg"]); diff --git a/tests/caring-contacts-message-copy.test.ts b/tests/caring-contacts-message-copy.test.ts index f8074b382f..fe8900163b 100644 --- a/tests/caring-contacts-message-copy.test.ts +++ b/tests/caring-contacts-message-copy.test.ts @@ -41,6 +41,16 @@ describe("caring-contacts patient-visible copy", () => { } }); + it("includes emergency escalation and therapeutic neutrality in both patient-visible strings", () => { + for (const text of [EXACT_PATIENT_VISIBLE_MESSAGE, AUTOMATED_REPLY_RESPONSE]) { + expect(text).toContain("In an emergency call 000"); + expect(text).not.toContain("?"); + for (const prohibited of ["high risk", "safe", "engagement score", "campaign", "lead", "conversion", "inbox"]) { + expect(text.toLowerCase()).not.toContain(prohibited); + } + } + }); + it("uses four distinct reserved fictional numbers", () => { expect(new Set(DESIGNATED_FICTIONAL_MOBILE_NUMBERS).size).toBe(4); expect(DESIGNATED_FICTIONAL_MOBILE_NUMBERS).toHaveLength(4); diff --git a/tests/guard-push.test.ts b/tests/guard-push.test.ts index 1905f57a64..c55621532b 100644 --- a/tests/guard-push.test.ts +++ b/tests/guard-push.test.ts @@ -261,6 +261,39 @@ describe("push-range parsing", () => { expect(changedFilesForRange({ localSha, remoteSha }, root)).toEqual(["feature.md", "post-merge.md"]); }); + it("resolves against merge-base when main advanced after the branch merged an earlier commit", () => { + const { root, git } = gitFixture(); + git("switch", "--quiet", "-c", "feature"); + writeFileSync(join(root, "feature.md"), "feature\n"); + git("add", "feature.md"); + git("commit", "--quiet", "-m", "feature"); + const remoteSha = git("rev-parse", "HEAD"); + + git("switch", "--quiet", "main"); + writeFileSync(join(root, "main-1.md"), "main 1\n"); + git("add", "main-1.md"); + git("commit", "--quiet", "-m", "advance main 1"); + const mergedMainSha = git("rev-parse", "HEAD"); + + git("switch", "--quiet", "feature"); + git("merge", "--quiet", "--no-edit", "main"); + writeFileSync(join(root, "post-merge.md"), "post merge\n"); + git("add", "post-merge.md"); + git("commit", "--quiet", "-m", "post merge"); + const localSha = git("rev-parse", "HEAD"); + + // Main advances again before the push: + git("switch", "--quiet", "main"); + writeFileSync(join(root, "main-2.md"), "main 2\n"); + git("add", "main-2.md"); + git("commit", "--quiet", "-m", "advance main 2"); + const latestMainSha = git("rev-parse", "HEAD"); + git("update-ref", "refs/remotes/origin/main", latestMainSha); + + expect(guardBaseForRange({ localSha, remoteSha }, root)).toBe(mergedMainSha); + expect(changedFilesForRange({ localSha, remoteSha }, root)).toEqual(["feature.md", "post-merge.md"]); + }); + // A force-push abandons the old remote tip. Comparing against it makes every // file the discarded history carried look deleted, which is unanswerable for // transaction guards; the merge base is the question CI actually asks. diff --git a/tests/therapy-compass-mode-wiring.test.ts b/tests/therapy-compass-mode-wiring.test.ts index f387bcb9c5..76e42000c7 100644 --- a/tests/therapy-compass-mode-wiring.test.ts +++ b/tests/therapy-compass-mode-wiring.test.ts @@ -53,7 +53,7 @@ describe("Therapy Compass production-mode wiring", () => { ); expect(appModesSrc).toContain('label: "Therapy"'); - expect(appModesSrc).toContain('placeholder: "Search therapies…"'); + expect(appModesSrc).toContain('placeholder: "Search therapies..."'); expect(appModesSrc).toContain('inputAriaLabel: "Search therapies by problem, symptom, skill, or population"'); expect(appModesSrc).toContain('submitAriaLabel: "Open Therapy"'); expect(sharedHomePresentation["therapy-compass"].title).toBe("Therapy"); From 284603b4b86f465f858b342e4156d892b685520d Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:09:28 +0800 Subject: [PATCH 2/5] docs: record review of tier-1-quick-wins --- ...dfb13d12a0eaa3e39c1e6d2b12d8352119ececa64d5c5f79095.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/6f453b76982e3dfb13d12a0eaa3e39c1e6d2b12d8352119ececa64d5c5f79095.record.md diff --git a/docs/branch-review-records/6f453b76982e3dfb13d12a0eaa3e39c1e6d2b12d8352119ececa64d5c5f79095.record.md b/docs/branch-review-records/6f453b76982e3dfb13d12a0eaa3e39c1e6d2b12d8352119ececa64d5c5f79095.record.md new file mode 100644 index 0000000000..34ac53ec1b --- /dev/null +++ b/docs/branch-review-records/6f453b76982e3dfb13d12a0eaa3e39c1e6d2b12d8352119ececa64d5c5f79095.record.md @@ -0,0 +1 @@ +| 2026-08-23 | codex/tier-1-quick-wins | a3f272501b74195afa36734ba000b3f432c8aa1d | Tier 1 quick wins (10 tasks) | clean review (0 defects) | guard-push, session-start, caring-contacts, route-reachability, app-modes, style-contracts, rag-offline, lint, typecheck, design-system | From d28237ba2b4c3820601bbaa16d31f017b1ca1ef9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 23 Aug 2026 15:19:51 +0000 Subject: [PATCH 3/5] fix: align constellation redirect and pair hooks-guard path checks Send /ward-management/constellation to /ward-management/network so the proxy fast-path matches the page backstop. Pair each core.hooksPath match with that tree's executable pre-push so the format guard cannot self-disable when Git will run an unwired hooks directory. --- .claude/hooks/push-format-guard.sh | 7 +++- src/proxy.ts | 9 ++-- tests/proxy.test.ts | 9 ++++ tests/push-format-guard.test.ts | 66 ++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 6 deletions(-) diff --git a/.claude/hooks/push-format-guard.sh b/.claude/hooks/push-format-guard.sh index 056d449c71..28f7da55b5 100755 --- a/.claude/hooks/push-format-guard.sh +++ b/.claude/hooks/push-format-guard.sh @@ -138,8 +138,11 @@ if [ -n "$hooks_path" ]; then esac # Exact equality, not a suffix match: another repository's `.githooks` also # ends in `/.githooks`, and its pre-push hook would not guard THIS push. - if { [ "$resolved" = "$expected" ] || [ "$resolved" = "$expected_primary" ]; } \ - && { [ -x "$repo_root/.githooks/pre-push" ] || [ -x "$primary_root_n/.githooks/pre-push" ]; }; then + # Pair each path match with THAT tree's pre-push: a match on the primary + # plus an executable hook only in the worktree (or the reverse) is the + # unwired case this guard exists to catch. + if { [ "$resolved" = "$expected" ] && [ -x "$repo_root/.githooks/pre-push" ]; } \ + || { [ "$resolved" = "$expected_primary" ] && [ -x "$primary_root_n/.githooks/pre-push" ]; }; then exit 0 fi fi diff --git a/src/proxy.ts b/src/proxy.ts index ba29387c9c..65454b55c9 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -48,10 +48,11 @@ const staticRouteRedirects: Record = { // and are now one route; `view`, `letter`, `topic` and `kind` mean the same // thing there, so the query string travels unchanged. "/dictionary/browse": "/dictionary/search", - // Ward Flow Constellation was retired into the coordinator screen in Phase 2; - // keep /ward-management/constellation as an intentional unlinked compatibility - // redirect preserving historical deep-links. - "/ward-management/constellation": "/ward-management", + // Ward Flow Constellation was retired in Phase 2; keep + // /ward-management/constellation as an intentional unlinked compatibility + // redirect to /ward-management/network so historical deep-links match the + // page backstop (PR #2303). + "/ward-management/constellation": "/ward-management/network", }; const publicPwaPaths = new Set(["/sw.js", "/offline.html", "/manifest.webmanifest", "/apple-icon", "/icon.svg"]); diff --git a/tests/proxy.test.ts b/tests/proxy.test.ts index 5401632c89..30ce7ba4a5 100644 --- a/tests/proxy.test.ts +++ b/tests/proxy.test.ts @@ -221,6 +221,15 @@ describe("developer-area header (x-developer-area)", () => { }); }); +describe("static compatibility redirects", () => { + it("forwards retired constellation deep-links to the network destination", async () => { + const response = await proxy(requestFor("/ward-management/constellation")); + expect(response.status).toBe(307); + const location = new URL(response.headers.get("location")!); + expect(location.pathname).toBe("/ward-management/network"); + }); +}); + describe("document-source fallback redirects", () => { const demoId = "11111111-1111-4111-8111-111111111111"; diff --git a/tests/push-format-guard.test.ts b/tests/push-format-guard.test.ts index 8ee589831c..d58988bdff 100644 --- a/tests/push-format-guard.test.ts +++ b/tests/push-format-guard.test.ts @@ -160,6 +160,38 @@ describe.skipIf(process.platform === "win32")("push-format-guard", () => { expect(out.stdout).toBe(""); }); + it("stays silent from a linked worktree whose primary .githooks is wired", () => { + const primary = mkdtempSync(join(tmpdir(), "push-format-guard-primary-ok-")); + const worktreeParent = mkdtempSync(join(tmpdir(), "push-format-guard-wt-ok-")); + scratchRoots.push(worktreeParent, primary); + execFileSync("git", ["init", "-q"], { cwd: primary }); + execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: primary }); + execFileSync("git", ["config", "user.name", "test"], { cwd: primary }); + execFileSync("git", ["commit", "--allow-empty", "-q", "-m", "init"], { cwd: primary }); + + const worktree = join(worktreeParent, "worktree"); + execFileSync("git", ["worktree", "add", "-q", worktree, "HEAD"], { cwd: primary }); + + mkdirSync(join(worktree, "node_modules/prettier"), { recursive: true }); + mkdirSync(join(primary, ".githooks"), { recursive: true }); + const primaryHook = join(primary, ".githooks/pre-push"); + writeFileSync(primaryHook, "#!/usr/bin/env bash\nexit 0\n"); + chmodSync(primaryHook, 0o755); + + setHooksPath(worktree, join(primary, ".githooks")); + + const binDir = mkdtempSync(join(tmpdir(), "push-format-guard-bin-")); + scratchRoots.push(binDir); + const npx = join(binDir, "npx"); + writeFileSync(npx, '#!/usr/bin/env bash\necho "[warn] bad.js"\nexit 1\n'); + chmodSync(npx, 0o755); + + const out = runHook(worktree, binDir, "git push origin HEAD", worktree); + expect(out.denied).toBe(false); + expect(out.stdout).toBe(""); + expect(out.status).toBe(0); + }); + // Windows drive-letter paths are case-insensitive; Bash `=` is not. Git // reports whatever casing `npm install` happened to write, so a checkout // Claude Code knows as `D:/Database` can carry `core.hooksPath` of @@ -227,6 +259,40 @@ describe.skipIf(process.platform === "win32")("push-format-guard", () => { setHooksPath(root, `${join(root, ".githooks").toUpperCase()}`); expect(runHook(root, binDir).denied).toBe(true); }); + + it("denies when core.hooksPath matches the primary tree but only the worktree pre-push is executable", () => { + // Linked-worktree self-disable must pair the resolved hooks path with + // THAT tree's pre-push. Matching the primary checkout's .githooks while only the + // worktree hook is executable would skip the Prettier check for the + // directory Git will actually run. + const primary = mkdtempSync(join(tmpdir(), "push-format-guard-primary-")); + const worktreeParent = mkdtempSync(join(tmpdir(), "push-format-guard-wt-")); + scratchRoots.push(worktreeParent, primary); + execFileSync("git", ["init", "-q"], { cwd: primary }); + execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: primary }); + execFileSync("git", ["config", "user.name", "test"], { cwd: primary }); + execFileSync("git", ["commit", "--allow-empty", "-q", "-m", "init"], { cwd: primary }); + + const worktree = join(worktreeParent, "worktree"); + execFileSync("git", ["worktree", "add", "-q", worktree, "HEAD"], { cwd: primary }); + + mkdirSync(join(worktree, "node_modules/prettier"), { recursive: true }); + mkdirSync(join(primary, ".githooks"), { recursive: true }); + mkdirSync(join(worktree, ".githooks"), { recursive: true }); + const worktreeHook = join(worktree, ".githooks/pre-push"); + writeFileSync(worktreeHook, "#!/usr/bin/env bash\nexit 0\n"); + chmodSync(worktreeHook, 0o755); + + setHooksPath(worktree, join(primary, ".githooks")); + + const binDir = mkdtempSync(join(tmpdir(), "push-format-guard-bin-")); + scratchRoots.push(binDir); + const npx = join(binDir, "npx"); + writeFileSync(npx, '#!/usr/bin/env bash\necho "[warn] bad.js"\nexit 1\n'); + chmodSync(npx, 0o755); + + expect(runHook(worktree, binDir, "git push origin HEAD", worktree).denied).toBe(true); + }); }); describe("scope", () => { From c5c7c6b0cbbb017cb376046bd020f2eb841a54e5 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 23 Aug 2026 23:33:59 +0800 Subject: [PATCH 4/5] test(ui): align differential presentation placeholder with ASCII ellipsis in UI specs --- src/components/universal-search-command-mockups.tsx | 2 +- tests/ui-accessibility.spec.ts | 2 +- tests/ui-tools.spec.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/universal-search-command-mockups.tsx b/src/components/universal-search-command-mockups.tsx index 8e569912e8..e9c313bb4b 100644 --- a/src/components/universal-search-command-mockups.tsx +++ b/src/components/universal-search-command-mockups.tsx @@ -268,7 +268,7 @@ const modeConfigs: Record = { id: "differentials", label: "Differentials", icon: BrainCircuit, - placeholder: "Ask or search a presentation", + placeholder: "Ask or search a presentation...", defaultQuery: "confusion", examples: ["acute confusion", "first episode psychosis", "catatonia vs NMS"], recents: ["confusion", "serotonin syndrome", "alcohol withdrawal"], diff --git a/tests/ui-accessibility.spec.ts b/tests/ui-accessibility.spec.ts index 3dd877d18c..587a6a8f04 100644 --- a/tests/ui-accessibility.spec.ts +++ b/tests/ui-accessibility.spec.ts @@ -483,7 +483,7 @@ test.describe("Clinical KB accessibility coverage", () => { // Retry fill-then-enabled together: the server-rendered composer is visible // before React controls it, and a fill landing in that gap is discarded by // hydration, leaving the search button disabled and the click a no-op. - const presentationInput = page.locator('input[placeholder="Ask or search a presentation"]:visible').first(); + const presentationInput = page.locator('input[placeholder="Ask or search a presentation..."]:visible').first(); const differentialSubmit = page.locator('button[aria-label="Search differential presentations"]:visible'); await expect(async () => { await presentationInput.fill("acute confusion"); diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 4c5ce86a12..c816483aa4 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -80,7 +80,7 @@ function waitForDifferentialCatalogQuery(page: Page, query: string) { } async function submitDifferentialSearch(page: Page, query: string) { - const input = page.locator('input[placeholder="Ask or search a presentation"]:visible').first(); + const input = page.locator('input[placeholder="Ask or search a presentation..."]:visible').first(); const submit = page.locator('button[aria-label="Search differential presentations"]:visible'); // Own the fill here rather than leaving it to callers. The server-rendered @@ -1897,7 +1897,7 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { await expect(visibleGlobalSearchInput(page)).toHaveCount(1); const heroSearch = sharedHome.getByTestId("global-search-input"); await expect(heroSearch).toBeVisible(); - await expect(heroSearch).toHaveAttribute("placeholder", "Ask or search a presentation"); + await expect(heroSearch).toHaveAttribute("placeholder", "Ask or search a presentation..."); // The hero heading sits above the composer, and the composer stays in the // upper two thirds — the same geometry contract every mode home is held to. @@ -2345,7 +2345,7 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { await page.setViewportSize({ width: 390, height: 844 }); await gotoLauncher(page, "/differentials"); - const input = page.locator('input[placeholder="Ask or search a presentation"]:visible'); + const input = page.locator('input[placeholder="Ask or search a presentation..."]:visible'); const submit = page.locator('button[aria-label="Search differential presentations"]:visible'); await expect(input).toHaveCount(1, { timeout: 15_000 }); await expect(submit).toHaveCount(1, { timeout: 15_000 }); From c6d2a1916eaf7a7fef3c7dc0827665334af54f19 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 23 Aug 2026 16:08:51 +0000 Subject: [PATCH 5/5] fix: coalesce signed-image automatic retries across sibling mounts Unit coverage failed on this head because two SignedImage instances scheduled independent retry timers. The first retry settled and dropped the in-flight map before the second timer fired, so fetch() ran a third time and the one-shot mock returned undefined. Share one retry timer per endpoint so siblings retry in the same macrotask and reuse the in-flight request. Align the CI concurrency example and Dev Drive / PreCompact docs with the checked-in contracts. Co-authored-by: BigSimmo --- docs/ci-operations.md | 2 +- docs/testing.md | 4 +- .../clinical-dashboard/signed-image.tsx | 49 +++++++++++++++++-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/docs/ci-operations.md b/docs/ci-operations.md index 6115ad3700..942e1be4cb 100644 --- a/docs/ci-operations.md +++ b/docs/ci-operations.md @@ -7,7 +7,7 @@ In PR #2209 (merged `af2075a`), GitHub Actions workflow concurrency for base-bra ```yaml concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && github.run_id || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + cancel-in-progress: ${{ github.event_name != 'push' }} ``` ### Background and Root Cause Closed diff --git a/docs/testing.md b/docs/testing.md index 27ded981f2..2ec6a5f372 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -65,11 +65,11 @@ export PLAYWRIGHT_KEEP_BUILD_ROOT=true ### Dev Drive trusted package cache verification (#6SMMB4) -On Windows workstations hosting worktrees on a Dev Drive (`D:`, ReFS) where `npm config get cache` resolves to `D:\.npm-cache`, Microsoft Defender real-time scanning can be bypassed for the package cache by registering it as a trusted Dev Drive cache (`fsutil devdrv query /volume D:` or `fsutil devdrv trust D:\.npm-cache`). Note that querying volume configuration with `fsutil` requires an elevated administrator command prompt; non-elevated prompts return `Error 5: Access is denied`. +On Windows workstations hosting worktrees on a Dev Drive (`D:`, ReFS) where `npm config get cache` resolves to `D:\.npm-cache`, register the whole volume as a trusted Dev Drive (`fsutil devdrv query D:` and `fsutil devdrv trust D:`). Trusting the volume enables Defender performance mode with asynchronous scanning; it does not disable Defender. Querying or trusting a Dev Drive requires an elevated administrator command prompt; non-elevated prompts return `Error 5: Access is denied`. ### PreCompact hook contract and logging (#RZQQBT) -`.claude/hooks/precompact-issues-capture.sh` is log-only and write-isolated, appending firing receipts to `.git/claude-precompact.log`. Claude Code surfaces hook stdout to model context for `SessionStart` and `UserPromptSubmit`; for `PreCompact`, the hook acts as a silent audit log and human transcript notice, guaranteed never to throw an unhandled error or break an automatic or manual context compaction. +`.claude/hooks/precompact-issues-capture.sh` is log-only and write-isolated. It appends firing receipts to `claude-precompact.log` under the directory from `git rev-parse --git-dir` (the worktree git dir for linked worktrees, not a hardcoded `.git/` path) and stays silent on stdout and stderr. Claude Code surfaces hook stdout to model context for `SessionStart` and `UserPromptSubmit`; `SessionStart` is the post-compaction backstop. The PreCompact hook is a silent audit log, guaranteed never to throw an unhandled error or break an automatic or manual context compaction. **Refuted levers (do not revive):** persistent Actions cache for the Next webpack tree (~804 MB, evicts browser cache); transporting the critical job's 1.09 GB webpack cache to three shard runners (CI 31285952061 spent 19–67s downloading it and the slowest runner was slower than a cold build); splitting `ui-phone-scroll*` to rebalance `--shard` (siblings still co-land); renaming specs to game alphabetical shard order; Playwright `workers > 1` or blocking retries; dropping Production UI from ordinary UI PRs; Firefox/WebKit on every PR (main/weekly matrix only). diff --git a/src/components/clinical-dashboard/signed-image.tsx b/src/components/clinical-dashboard/signed-image.tsx index 5ae2107de0..51c9c8bd5b 100644 --- a/src/components/clinical-dashboard/signed-image.tsx +++ b/src/components/clinical-dashboard/signed-image.tsx @@ -12,6 +12,13 @@ import { ImageLightbox } from "@/components/clinical-dashboard/image-lightbox"; const AUTOMATIC_RETRY_DELAYS_MS = [250, 1_000] as const; +type SharedAutomaticRetry = { + timeoutId: number; + callbacks: Set<() => void>; +}; + +const sharedAutomaticRetries = new Map(); + function automaticRetryDelay(failure: SignedImageFailure | null, attempt: number) { const baseDelay = AUTOMATIC_RETRY_DELAYS_MS[attempt]; if (!failure?.retryable || baseDelay === undefined) return null; @@ -22,6 +29,41 @@ function automaticRetryDelay(failure: SignedImageFailure | null, attempt: number return Math.max(baseDelay, failure.retryAfterMs ?? 0); } +/** + * One timer per endpoint so sibling `SignedImage` mounts retry in the same + * macrotask. Separate per-instance timeouts let the first retry settle and + * drop the in-flight map before the second timeout fires, which then starts a + * second fetch (and exhausts one-shot test mocks). + */ +function scheduleSharedAutomaticRetry(endpoint: string, delayMs: number, callback: () => void): () => void { + const existing = sharedAutomaticRetries.get(endpoint); + if (existing) { + existing.callbacks.add(callback); + return () => { + existing.callbacks.delete(callback); + if (existing.callbacks.size === 0) { + window.clearTimeout(existing.timeoutId); + sharedAutomaticRetries.delete(endpoint); + } + }; + } + + const callbacks = new Set<() => void>([callback]); + const entry: SharedAutomaticRetry = { callbacks, timeoutId: 0 }; + entry.timeoutId = window.setTimeout(() => { + sharedAutomaticRetries.delete(endpoint); + for (const run of callbacks) run(); + }, delayMs); + sharedAutomaticRetries.set(endpoint, entry); + return () => { + callbacks.delete(callback); + if (callbacks.size === 0) { + window.clearTimeout(entry.timeoutId); + sharedAutomaticRetries.delete(endpoint); + } + }; +} + /** * Shared renderer for a private image served through a signed-URL endpoint. * @@ -135,14 +177,13 @@ export const SignedImage = memo(function SignedImage({ useEffect(() => { if (nextAutomaticRetryDelay === null) return () => undefined; - const timer = window.setTimeout(() => { + return scheduleSharedAutomaticRetry(endpoint, nextAutomaticRetryDelay, () => { setLoaded(false); setShouldLoad(true); setAutomaticRetryCount((current) => current + 1); retry(); - }, nextAutomaticRetryDelay); - return () => window.clearTimeout(timer); - }, [nextAutomaticRetryDelay, retry]); + }); + }, [endpoint, nextAutomaticRetryDelay, retry]); function retryImage() { if (retryDisabled) return;