diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index c75b8f57a..cb639b3c7 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -961,5 +961,9 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | execute-audit-code-remediation (PR #1162) | b9b56c140eb14cbba5a2c2230e3fa28d3a791add | CI unblock after bot sync | Tip 96188eca had PR required SUCCESS (Static/Safety/Unit/Build/Migration/Production UI). Hosted pr-branch-sync then merged main (a420b86b/b9b56c14), leaving CI action_required for bot-authored runs. Pushing agent commit to re-trigger non-bot CI. | Prior tip 96188eca hosted CI green; local services referral Playwright PASS; no provider-backed checks. | | 2026-07-25 | PR supersede #1186 / `cursor/pr1186-audit-remediation-c94c` | `a38e83860510a4229d5658960657cd7448aff278` | Clean main-based port of intentional #1186 audit fixes | SUPERSEDE #1186 (do not merge old PR). Ported intentional 16-file delta onto current main; dropped conflicted checkpoint tree and placeholder skills. Fixed eval single results binding; async run-heavy so lock heartbeat fires; branch:cleanup dry-run default + argv-safe deletes; skill-create interface YAML. Close #1186. | Focused Vitest tooling+lock 6/6; check:skills 33; prettier on touched files; no provider/live eval runs. | | 2026-07-25 | `cursor/ledger-066-067-519b` | f04392a408eceee14215c15169cbd6b70ac2041c | Close stale ledger #066/#067 (+ drop resolved #030/#075 from queue) | READY. #066 proven on main via #1174; #067 already fixed in #1191 in-process preflight. Docs-only ledger sync; no code change. | Local proof: `git show`/`log` for #1174/#1191; preflight test already in-process on main; ledger integrity asserts. No providers. | +| 2026-07-25 | PR #1190 / `remediate-dark-mode-audit` | `00eca49b9b0d7e5fbfa5703a15e9e930963984a6` | Cursor review+Bugbot+prlanded+debug (fresh pass, same HEAD) | DO NOT MERGE; NOT LANDED (OPEN, mergeable=CONFLICTING/DIRTY, 468 behind / 2 ahead). Reconfirmed P0: conflict resolution deleted `trustGatedAnswerForClinicalNotes` (0 hits on head; main L584/632/659/1075) — Clinical Notes consumes ungated answer. P1: `src/app/api/answer/route.ts:6` imports nonexistent `@/lib/rag` (tsc TS2307; stream correctly uses `@/lib/rag/rag`). P1: merge-tree conflicts on answer/upload/evidence-panels + 10 paths; literal `<<<<<<<` in docs audit plan; migration timestamp collision risk vs main. Intentional dark-mode delta is only commit `363672602` (~10 files). Salvage: `cursor/pr1190-dark-mode-salvage-f453` cherry-picks that commit onto current main, restores unused-manifest-import cleanup, keeps clinical gate. Close #1190 after salvage lands. | Bugbot; `git grep` gate/import/markers; merge-tree; `tsc` TS2307 proof; gh pr view/checks (PR policy fail). Salvage: tsc clean; visual-evidence+overlay tests 13/13; eslint on changed files. No provider/UI matrix. | | 2026-07-25 | execute-audit-remediation-plan (PR #1188) | 8b8639113925601e1687bfe4f1f29c44a4308b61 | Bugbot/diff-review: maintainability remediation tip | BLOCK: tip tree carries unresolved conflict markers (answer/upload APIs, clinical-dashboard, services, tests); invalid `async export function sha256Hex` in indexing-v3 utils; ClinicalDashboard still calls removed `renderSystemNotice`; merge-tree vs main conflicts in check-github-action-pins.mjs + ui-primitives.tsx. Intended notices extraction/dynamic imports look mostly sound; search-scope/migration not in three-dot product delta. | `git grep` conflict markers on tip (none on origin/main); `git show` for ClinicalDashboard:3824 + utils.ts:191; `git merge-tree --write-tree origin/main 8b8639113`; no provider-backed checks. | | 2026-07-25 | PR #1209 / `cursor/pr1186-audit-remediation-c94c` | `4cb22e45dfe46b1975fa15ddd028c7e14ceb5fab` | Close #1186 + babysit #1209 CI | DONE. Closed #1186 as superseded. Synced origin/main (MERGEABLE/CLEAN). Fixed PR-policy RAG impact line. Hosted PR required SUCCESS (Static/Safety/Unit/Build/Production UI/Advisory UI/containers) on pre-ledger tip; docs-only follow-up pushed. Ready to merge; auto-merge not enabled. | gh pr checks; local policy ok; no provider/eval runs. | +| 2026-07-25 | cursor/pr1190-dark-mode-salvage-f453 (PR #1214) | pending-ci-retrigger | CI unblock after bot sync | Hosted pr-branch-sync merged main onto tip (`68d0ceaab`), leaving CI `action_required` for bot-authored runs. Pushing agent commit to re-trigger non-bot CI before squash-merge; then close #1190. | Local pwa-manifest 8/8; trust gate present; prior unit failure fixed. No provider-backed checks. | + +| 2026-07-25 | cursor/pr1190-dark-mode-salvage-f453 (PR #1214) | pending-ci-retrigger-2 | CI unblock + skip-branch-sync | Repeated pr-branch-sync bot merges left CI `action_required`. Applied `skip-branch-sync` label and agent retrigger so required checks can finish for squash-merge; then close #1190. | Hosted CI pending on tip; no provider-backed checks. | diff --git a/eslint-rules/no-hardcoded-hex.mjs b/eslint-rules/no-hardcoded-hex.mjs new file mode 100644 index 000000000..2c7a5309f --- /dev/null +++ b/eslint-rules/no-hardcoded-hex.mjs @@ -0,0 +1,41 @@ +/** + * Local ESLint rule: forbid hardcoded hex Tailwind colour utilities such as + * `bg-[#…]`, `text-[#…]`, and `border-[#…]` so themes must use semantic CSS + * variables (e.g. `var(--surface)`). + */ + +const rule = { + meta: { + type: "problem", + docs: { + description: "Prevent hardcoded hex color utilities in Tailwind classNames", + }, + schema: [], + }, + create(context) { + const hexPattern = /\b(bg|text|border)-\[#[0-9a-fA-F]{3,8}\]/; + + return { + Literal(node) { + if (typeof node.value === "string" && hexPattern.test(node.value)) { + context.report({ + node, + message: + "Hardcoded hex color utilities (bg-[#...], text-[#...], border-[#...]) are forbidden. Use semantic CSS variables (e.g. var(--surface)) to support theming.", + }); + } + }, + TemplateElement(node) { + if (node.value && node.value.raw && hexPattern.test(node.value.raw)) { + context.report({ + node, + message: + "Hardcoded hex color utilities (bg-[#...], text-[#...], border-[#...]) are forbidden. Use semantic CSS variables (e.g. var(--surface)) to support theming.", + }); + } + }, + }; + }, +}; + +export default rule; diff --git a/eslint.config.mjs b/eslint.config.mjs index b85dedc10..beb2d633d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,6 +5,7 @@ import nextTs from "eslint-config-next/typescript"; import requireLucideIconAria from "./eslint-rules/require-lucide-icon-aria.mjs"; import requireButtonWiring from "./eslint-rules/require-button-wiring.mjs"; import restrictSuppressHydrationWarning from "./eslint-rules/restrict-suppress-hydration-warning.mjs"; +import noHardcodedHex from "./eslint-rules/no-hardcoded-hex.mjs"; // Shared `local` plugin object. ESLint flat config requires every config block // that references a plugin namespace to point at the *same* object, so the two @@ -14,6 +15,7 @@ const localRulesPlugin = { "require-lucide-icon-aria": requireLucideIconAria, "require-button-wiring": requireButtonWiring, "restrict-suppress-hydration-warning": restrictSuppressHydrationWarning, + "no-hardcoded-hex": noHardcodedHex, }, }; @@ -66,6 +68,7 @@ const eslintConfig = defineConfig([ plugins: { local: localRulesPlugin }, rules: { "local/require-button-wiring": "error", + "local/no-hardcoded-hex": "error", }, }, { diff --git a/playwright.config.ts b/playwright.config.ts index 897d06694..0228bef59 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,9 @@ import { defineConfig, devices } from "playwright/test"; +import { stableProjectPort } from "./src/lib/local-server-utils.mjs"; import { getPlaywrightBaseUrl } from "./scripts/playwright-base-url"; +process.env.PORT = process.env.PORT || String(stableProjectPort(process.cwd())); + const baseURL = getPlaywrightBaseUrl({ allowEnsure: false }); // Sandboxed CI/cloud containers often ship a preinstalled Chromium and block diff --git a/src/app/globals.css b/src/app/globals.css index da1e65847..4ed855414 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2690,3 +2690,33 @@ html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .sou transition: none; } } + +/* Theme switch micro-interaction (Improvement 2.2) */ +html.theme-transitioning, +html.theme-transitioning *, +html.theme-transitioning *:before, +html.theme-transitioning *:after { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke !important; + transition-duration: 200ms !important; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important; +} + +@media (prefers-reduced-motion: reduce) { + html.theme-transitioning, + html.theme-transitioning *, + html.theme-transitioning *:before, + html.theme-transitioning *:after { + transition: none !important; + } +} + +/* Windows High Contrast / Forced Colors Compatibility (Improvement 2.3) */ +@media (forced-colors: active) { + :root, + .dark { + --border: CanvasText; + --border-lux: CanvasText; + --border-strong: CanvasText; + --focus: Highlight; + } +} diff --git a/src/app/manifest.ts b/src/app/manifest.ts index 2c8b94b61..a0f096697 100644 --- a/src/app/manifest.ts +++ b/src/app/manifest.ts @@ -1,10 +1,10 @@ import type { MetadataRoute } from "next"; -import { APP_THEME_COLORS, DEFAULT_THEME } from "@/lib/theme"; // PWA manifest — makes the app installable with a proper icon. Icons derive from // the single brand-mark source: the SVG for modern browsers, plus generated PNG -// "any" and "maskable" sets from app/icons/[variant]. Colours match the light -// default of viewport.themeColor in app/layout.tsx. +// "any" and "maskable" sets from app/icons/[variant]. Theme colours stay on +// viewport.themeColor / meta theme-color (see app/layout.tsx and use-theme.ts) +// so light/dark can update without a static PWA manifest colour lock. export default function manifest(): MetadataRoute.Manifest { return { name: "Clinical KB", @@ -22,8 +22,6 @@ export default function manifest(): MetadataRoute.Manifest { // Focus the already-open app window on launch instead of spawning a second // instance; "auto" lets platforms without the capability use their default. launch_handler: { client_mode: ["navigate-existing", "auto"] }, - background_color: APP_THEME_COLORS[DEFAULT_THEME], - theme_color: APP_THEME_COLORS[DEFAULT_THEME], categories: ["medical", "productivity", "utilities"], prefer_related_applications: false, icons: [ diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx index b8b162c0f..b95f8065e 100644 --- a/src/components/clinical-dashboard/ClinicalSidebar.tsx +++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx @@ -311,7 +311,7 @@ export function ClinicalSidebarContent({ className="mt-2 flex w-full items-center gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-2 text-left shadow-[var(--shadow-inset)] transition hover:border-[color:var(--clinical-accent-border)] hover:bg-[color:var(--clinical-accent-soft)]/40 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]" aria-label={accountLabel} > - + {identity.initials} diff --git a/src/components/clinical-dashboard/provider-brand-icons.tsx b/src/components/clinical-dashboard/provider-brand-icons.tsx index aa1c64512..8670a81c3 100644 --- a/src/components/clinical-dashboard/provider-brand-icons.tsx +++ b/src/components/clinical-dashboard/provider-brand-icons.tsx @@ -103,10 +103,10 @@ export function ProviderBrandMark({ provider }: { provider: SsoProvider }) { className="grid h-7 w-7 shrink-0 grid-cols-2 gap-0.5 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-1 shadow-[var(--shadow-inset)]" aria-hidden="true" > - - - - + + + + ); } @@ -116,8 +116,9 @@ export function ProviderBrandMark({ provider }: { provider: SsoProvider }) { aria-hidden="true" className={cn( "grid h-7 w-7 shrink-0 place-items-center rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] text-base font-bold leading-none shadow-[var(--shadow-inset)]", - provider === "Apple" ? "text-[color:var(--text-heading)]" : "text-[#4285f4]", + provider === "Apple" ? "text-[color:var(--text-heading)]" : "", )} + style={provider === "Google" ? { color: "#4285f4" } : undefined} > {provider === "Apple" ? "A" : "G"} diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index 55b006f08..020600b44 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -283,7 +283,7 @@ export function SettingsDialog({ type="button" onClick={onClose} aria-label="Close settings" - className="grid h-9 w-9 shrink-0 place-items-center rounded-full border border-[color:var(--border)] bg-[color:var(--surface)]/70 text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] transition hover:bg-[color:var(--surface)] hover:text-[color:var(--text-heading)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] lg:h-10 lg:w-10 lg:border-transparent lg:bg-transparent lg:shadow-none" + className="grid size-tap shrink-0 place-items-center rounded-full border border-[color:var(--border)] bg-[color:var(--surface)]/70 text-[color:var(--text-muted)] shadow-[var(--shadow-inset)] transition hover:bg-[color:var(--surface)] hover:text-[color:var(--text-heading)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] lg:h-10 lg:w-10 lg:border-transparent lg:bg-transparent lg:shadow-none" >