Skip to content

On Call mode — a junior doctor's shift hub - #2608

Merged
BigSimmo merged 32 commits into
mainfrom
claude/on-call-mode-health-n17dvm
Sep 4, 2026
Merged

On Call mode — a junior doctor's shift hub#2608
BigSimmo merged 32 commits into
mainfrom
claude/on-call-mode-health-n17dvm

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a seventeenth app mode, on-call, giving a junior doctor one place for the operational knowledge a shift needs: orientation manuals, role-based contacts, referral pathways, teaching, an escalation playbook, and site logistics.

All fourteen planned tasks have landed: the domain model and twelve-month freshness rule, the on_call_entries table, the owner-scoped repository and API routes, registration across every mode-keyed map, navigation, the route tree and shared section shell, the offline entry cache, all six section UIs, in-app entry editing, cross-section search, the printable essentials card, and the generated artefacts and docs.

Design and plan travel with the code: docs/superpowers/specs/2026-09-04-on-call-mode-design.md and docs/superpowers/plans/2026-09-04-on-call-mode.md.

Decisions worth a reviewer's attention:

  • Owner-scoped private data, edited in the app. Not committed into the repository. public.on_call_entries follows the clinical_registry_records template — owner_id NOT NULL, service-role-only RLS, application-layer owner scoping on the same fluent chain so check:owner-scope can prove it. This table has no public state by design and must never gain one.
  • v1 is owner-only; cohort sharing is deferred to its own project. The app has two visibility states, owner-only and fully public, and the site has no login wall, so the only available "share" today would publish a hospital's internal contacts to the open internet.
  • The playbook never authors clinical guidance in the app's voice. Clinical content appears only as links to documents in the owner's own corpus, each shown with title and date. A scenario with no linked local guideline renders an explicit "no local guideline" state rather than substituting a generated answer.
  • Twelve-month freshness, derived at read time and never stored, so changing the interval needs no migration or backfill. Leap-day anniversaries are clamped rather than rolling into the next month; the boundary counts as overdue; and a date that cannot be parsed fails to stale, never to fresh.
  • Staleness is visible wherever a number is. Section rows, search results and the printable card all read the same derivation; the card excludes stale entries outright, because paper cannot show its own age.
  • A partial save is rejected, because an omitted field previously reset silently to its default — worst of all the last-checked date, which is the record that an entry was confirmed correct.
  • Out of scope, deliberately: rosters, hosted recordings, any patient information.

What the reviews changed

Two independent reviews ran against this branch, and both found real defects. Recording them because the diff alone does not show what was nearly shipped.

The whole-branch review found that only Contacts was wired to the entry store. The five other list components were imported by their own tests and by nothing else in src/, so those five pages reported "no entries yet" regardless of what the owner had saved, offered no way to add anything, and left the Playbook's "no local guideline linked" state unreachable — an entry would appear in search and be absent from its own page. Each section id now resolves to its list component through one exhaustive switch with no default arm, so a seventh section is a compile error rather than another silently blank page. The same review also caught an account switch that left the previous user's cached entries in place (sign-out cleared them; switching accounts did not, and a shared ward computer switches accounts without signing out), a freshness check that failed open on an unparseable date and so printed such an entry on the card, a storage-blocked browser reporting an empty hub after a successful fetch, a stale referral whose marker was hidden until its disclosure was expanded, and three cards that hand-rolled the shared cardSurface recipe without forced-colors:border.

The per-task review found search matching case-sensitively — every haystack handed to rankCatalogRecords must already be normalised, and Orientation carries its content in body alone, so it was effectively unsearchable — plus a result count reading "3 on Call", an empty account told to "check the spelling", and the card claiming "No number on file" against playbook steps that have no number by design.

Two test fixtures authored clinical content (a temperature threshold, an escalation rule). Both are now content-free: a fixture is how such content usually enters a repository.

Navigation: On Call is not a mode-nav mode

Worth flagging because an earlier revision of this branch got it wrong in a way that hid a real defect.

All six On Call routes are information pages, and PageSecondaryNavigation returns null for those, so RegistryModeNav never mounts on any of them. The branch initially registered On Call in MODE_NAV_ADOPTED_MODES with a six-item extended density profile, and the phone density spec then failed — not because six labels clipped, but because the bar was never in the DOM to measure. A four-label fallback would have failed identically.

So the registration is removed: "on-call": [] in the secondary-navigation registry, no density profile, no icon entries. Removing it exposed what the registration had been masking — the registry was satisfying route-reachability with links nothing ever drew, so five On Call pages had never been reachable in the app. They are now reached by six literal <Link href="/on-call/..."> elements in the section switcher, literal because the guard resolves hrefs statically and neither an interpolated path nor a wrapper component taking href as a prop counts.

Verification

  • npm run verify:pr-local

Full offline unit suite on the current head: 16,800 passed, 0 failed across 1,145 files. Lint and whole-project typecheck clean. A real production build exits 0 and emits all nine On Call pages (/on-call, six sections, /on-call/search, /on-call/card) plus the three API routes; typecheck alone does not catch Route Handler export violations, so the build is run rather than inferred.

supabase/drift-manifest.json has been regenerated and committed. npm run drift:manifest needs the digest-pinned Postgres image, and the authoring environment's egress policy denies the registry blob fetch from every mirror tried as well as the Actions artifact download, so the manifest came from the CI job that regenerates it on a Docker-capable runner (run 33888898898) and was supplied by the operator. It was checked before landing rather than trusted: its schema_sha256 is byte-equal to the value tests/drift-detection.test.ts derives from this tree's supabase/schema.sql, and its snapshot genuinely carries on_call_entries rather than only a bumped hash — updating the hash alone would have made the gate compare the live clinical database against a baseline without the new table, which is green and wrong. tests/drift-detection.test.ts passes 21/21.

Three suites (privacy-readiness-contract, clinical-hazard-controls, rag-plan-package-parity) initially failed in the authoring environment because its clone was shallow and they resolve real commit SHAs. They pass unchanged with full history; nothing in them needed fixing.

Browser coverage runs in CI, which executes the same suite. One fix here is worth naming: the app mode menu keyboard test raced its own menu. Opening the menu schedules the search autofocus in a requestAnimationFrame; focusing an option before that frame ran let the autofocus steal focus back into the search box, where Tab is not a dismiss key, so the menu stayed open and the assertion timed out ten seconds later. Measured in a browser rather than guessed: from the search box, Tab moves focus to the first option, which is inside the menu wrapper, so the wrapper's onBlur never sees a focus-leave. The test now waits for the autofocus to settle and asserts the option holds focus before pressing the key whose behaviour depends on it. Main reached the same fix independently, which is what the merge conflict in tests/ui-smoke.spec.ts was; the assertions were identical on both sides and only the comment differed.

Known and deliberately deferred: linked_document_ids carries no foreign key, so a deleted document leaves an id pointing at nothing. The behaviour is conservative — it reads as "no local guideline linked" rather than a dangling reference — and adding the constraint means a second migration on a PR that already carries one.

Risk and rollout

  • Risk: medium. This PR adds a schema migration, and merging applies it to the live clinical database within seconds. The application code is additive — a new mode, new routes, new API surface — and touches existing behaviour only through mode-keyed registries that are exhaustive by type.
  • Rollback: revert the merge commit; the table is additive and unreferenced by existing features.
  • Provider or production effects: the migration creates one owner-scoped table. No provider calls were made from this session.
  • RAG impact: no retrieval behaviour change — this adds an owner-scoped operational content mode whose search filters an already-fetched entry set in the browser, changing nothing in retrieval, ranking, the RPCs, or the eval fixtures.

Merge decision: merge only inside an approved window, per AGENTS.md "Supabase project safety". Auto-merge must not be armed on a migration-bearing PR; it was armed earlier on this PR and the repository owner has since disabled it.

Clinical Governance Preflight

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

How each was satisfied: the playbook and orientation summaries are specified and tested to link source documents rather than restate them, and an unresolvable link reads as "no local guideline linked" rather than promoting a scenario to "has guidance"; the mode stores no patient information and the spec records that as a boundary; the Supabase target is unchanged; the API resolves the owner from the validated session and never from a request body, with the service-role client confined to the server, and the cached entries are now cleared on an account switch as well as on sign-out; demo fixtures are obviously synthetic and every test fixture was rewritten to carry no clinical claim; stale entries surface loudly, are excluded from the printable card, and an unreadable date fails to stale; and no clinical decision-support behaviour is introduced — this mode is operational reference and is specified never to produce clinical guidance of its own.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC

Captures the agreed design for a seventeenth app mode giving a junior
doctor one place for shift-operational knowledge: orientation manuals,
role-based contacts, referral pathways, teaching, an escalation
playbook, and site logistics.

Records the decisions and what each rules out, notably:

- Content is owner-scoped private data edited in the app, not committed
  into the repository.
- v1 is owner-only. Cohort sharing is deferred to its own project: the
  app has two visibility states, owner-only and fully public, and the
  site has no login wall, so the only available share would publish
  internal service information to the open internet.
- The playbook links to the owner's own uploaded guidelines and never
  authors clinical guidance in the app's voice; a scenario with no local
  guideline says so rather than substituting a generated answer.
- Every entry carries a last-checked date and shows an unmissable stale
  state after twelve months.
- No rosters, no hosted recordings, no patient information.

Also records the migration-timing constraint: merging reaches the live
clinical database within seconds, so the migration merges only inside an
approved window with auto-merge unarmed.

The user-experience section is still to be written and will follow the
design-system system of record and the phone chrome contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: f2839045-db2d-49e7-891b-547c65472040


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Sep 4, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

Both were written from the general pattern rather than from the files,
and both were wrong in a way that would have cost time in the plan:

- The offline contacts cache reuses the existing `createBrowserStore`
  helper, following `saved-registry-storage.ts`, rather than
  introducing a storage mechanism the app does not need.
- Reads resolve the caller through `publicAccessContext`, which already
  handles the anonymous case for the registry routes; writes require an
  authenticated user. The owner still never comes from the request body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
The design section was written from the repository's own design-system
system of record and phone chrome contract rather than from general
practice, which corrected two things the earlier draft had wrong:

- Mode homes live on the shared home with the mode path redirecting to
  it. The draft had On Call owning a real home. Following the
  convention removes work rather than adding it.
- Six navigation labels are not automatically allowed; they need
  recorded density evidence. The spec now carries the fallback in
  advance, so nobody makes a fresh decision mid-build.

The section commits On Call to borrowing the existing design language
rather than inventing one: no new colours, type sizes, spacing or
motion; the shared in-page navigation header rather than a second phone
header; 48px tap targets, never 44. It also specifies the states that
carry safety weight — stale entries signalled by more than colour, the
offline banner announced through a screen-reader-only region, a faulted
search that renders no count, and a playbook scenario with no linked
local guideline rendering an empty state rather than a substitute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 2 failed job(s):

  • Lighthouse budgetnot baselined: this job did NOT run on the main comparison below (path-scoped skip), so that run says nothing about it either way. Treat the comparison as absent, not green, and inspect the failing step.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #15613 (success). That run's conclusion is an aggregate and did not exercise Lighthouse budget.

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

`Static PR checks` went red on `check:repo-awareness-snapshot`: adding
a documentation file leaves the committed snapshot behind the
repository. Regenerated with `npm run snapshot:repo-awareness` rather
than edited by hand, which is what the failure message asks for.

Verified locally: the gate now reports the snapshot in step, and the
four documentation gates that run after it in the same job — index
coverage, inventory, script references and links — all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
Fourteen tasks, seventy steps, each ending in an independently testable
deliverable and a commit. Written test-first: every task states the
failing test, the command that proves it fails, the implementation, and
the command that proves it passes.

The order is deliberate. The domain model and staleness come first
because they are pure and every later task depends on their names; the
table and the API follow; mode registration comes next because the
typecheck then enumerates the remaining registries itself rather than
relying on a hand-written list; the six section pages come last, built
on Contacts as the reference implementation.

Two things the plan settles in advance rather than leaving to the build:
whether six navigation labels fit a phone bar is answered by recorded
density evidence, with the fallback already chosen; and the migration's
merge timing is carried in the global constraints, because merging
reaches the live clinical database within seconds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
…nth staleness

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…xt month

When a verification date falls on February 29, adding 12 months via setUTCMonth
silently rolls into March 1 (February 2025 has no 29th), causing that entry to
stay fresh one day longer than all others. Capture day-of-month, detect overflow
by comparing after the shift, and clamp back to the last day of the intended
month using setUTCDate(0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the migration for on_call_entries (owner-scoped, RLS enabled,
service_role only), mirrors it into supabase/schema.sql, indexes the
new table in the codebase map, and adds the migration contract test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
…tory

Two plan tasks in one commit. They were built in parallel by separate
agents and are separable in principle, but the pre-commit hook refuses
a path-limited commit while the sibling documentation inputs it
regenerates are still outstanding, and splitting them would have meant
stashing one agent's work — the operation that had already caused one
silent revert earlier in this run.

Mode registration. `on-call` joins the sixteen existing modes in every
map keyed by mode id: the definition and search configuration, the
shared-home copy, the mode glyph and accent, the universal-search
context, the command surface, and the site-map generator. Most of these
are exhaustive by type, so the compiler enumerated the work rather than
a hand-written list. The secondary-navigation registry takes an empty
placeholder for now; the next task fills in its six destinations. The
glyph is a new key rather than a reused one, because mode glyphs must
be pairwise unique. Four tests holding complete sixteen-mode lists now
hold seventeen.

Entry repository. Maps a row to the domain entry and back, and fetches
an owner's entries with the owner predicate on the same fluent chain as
the table reference — the shape the owner-scope gate reads to prove a
query is scoped. Fetching without an owner throws rather than returning
rows, and a row whose details do not match its section renders without
them rather than as a half-parsed shape.

`on_call_entries` is also declared in the Supabase types file, which is
curated by hand in this repository: there is no regeneration script, it
carries a hand-written compatibility note, and the comparable registry
table is already declared there. Regeneration could not have produced
this entry anyway, since the table does not reach the live database
until the migration merges.

Verified: whole-project typecheck clean, 177 tests across the mode
registration suites, 3 for the repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
…le emptying the list

Navigation. On Call's six destinations join the secondary bar —
Contacts, Playbook, Referrals, Orientation, Teaching, Logistics, in
that order, because Contacts is the page a shift actually opens. Each
carries an icon, since that map is exhaustive by type. The mode joins
the phone sheet's Care group and the sidebar's pinnable and more-modes
lists; without the first of those it would have been silently
unreachable on a phone, which is exactly what that test exists to
catch. The three shell routing helpers now recognise the namespace.

The density profile is `extended`, the one Therapy Compass uses, and
carries a comment recording that six labels is one more than any
existing adopted mode and that the phone-bar density evidence is owed
once the routes exist. The fallback, if six do not fit, is already
decided in the plan rather than left to be invented later.

Also adds `desktopSearchPlacement: "hero"`, which the plan omitted and
every sibling mode sets.

Subtitle handling. The database permits an empty subtitle, but the
entry schema required a non-empty string, and the parse runs inside a
map over every row — so one blank subtitle rejected the entire fetch
and would have left an on-call doctor with no numbers at all rather
than one untidy row. Empty subtitles now normalise to absent. The parse
still throws on a genuinely malformed row rather than silently dropping
it: degrade conservatively, do not guess.

One test is knowingly red until the search page exists:
search-results-band-adoption asserts that a mode declaring a result
list must mount the shared band, which stops a failed search reporting
"0 matches". On Call declares the list; the band arrives with its
search page. Silencing it by weakening the declaration would defeat the
check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@BigSimmo
BigSimmo marked this pull request as ready for review September 4, 2026 11:04
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_2a0fa906-ac24-4382-b1cd-fe40b62a4f7c)

Add GET/POST /api/on-call/entries, PATCH/DELETE /api/on-call/entries/[id],
and POST /api/on-call/entries/[id]/verify. Every write scopes on
owner_id from the validated session (never body/query), a PATCH/DELETE/
verify for an id the caller does not own 404s identically to a missing
id, and demo mode serves a synthetic non-clinical fixture set without
touching Supabase. Adds a dedicated "on_call" rate-limit bucket and
indexes the new route area in docs/codebase-index.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@BigSimmo
BigSimmo enabled auto-merge September 4, 2026 11:07
…ject partial saves

Routes. Ten route files under /on-call: the redirect stub, layout,
loading, the six section pages, and a minimal search page. All six
sections render through one module rather than six divergent shells,
following the Sources mode's factoring, and each mounts the shared
in-page navigation header rather than a hand-built second phone header.
The search page is deliberately minimal for now; its content and the
shared results band arrive with the search task.

Two registrations the plan did not anticipate, both required rather
than discretionary. The section routes are declared as information
pages, without which the shared mode bar double-mounts alongside our
own header in the same slot. And the eight new routes are declared in
the design-system adoption contract, a static gate that fails closed on
an undeclared production route; they inherit the existing catalogue
surface's proof, so no new evidence was needed.

Partial saves. Editing an entry now requires the complete entry.
Previously a save that omitted a field silently reset it to a default
and returned success — worst of all for the last-checked date, which is
the record that an entry was confirmed correct and the thing the
twelve-month staleness design rests on. A partial body is now rejected
outright. Defaults remain correct on create, where a new entry that
omits a subtitle genuinely has none, and are dangerous only on update.

The results-band adoption test stays knowingly red until the search
page mounts the band.

Verified: typecheck and lint clean; 111 tests across route
reachability, loading contract, in-page navigation, nav slot ownership,
viewport fill and redirect behaviour; 11 on the API contract; owner
scope green on both phases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
Contacts is the page the mode exists for, so it is designed for one
hand in a corridor and the desktop view is that widened rather than a
separate layout. Each role is one row, the whole row is the tap target
at 48px, and the number is a tel: link, so ringing someone is a single
tap. Entries overdue for checking collect into a group at the top
rather than being buried.

Freshness is shown with an icon and the words "checked" plus a date,
never by colour alone — colour may reinforce the meaning but must not
carry it, because it fails for a colourblind reader and in
high-contrast mode. An entry never checked says so plainly instead of
showing a date it does not have.

Offline. The entry set is cached in the browser on a successful load
and rendered when the network fails, behind a banner naming the date
the copy was taken: a cached number with no age is worse than no
number. The cache clears on sign-out through the existing sign-out
path, so a hospital's internal numbers — some of them personal — do not
outlive the session on a shared machine. Every storage read and write
is wrapped, because a browser with site data blocked throws on access
and that must not reach render.

The row primitive is deliberately section-agnostic; the remaining five
sections reuse it rather than growing five variants.

One inference to flag: the design says contacts group by area, but the
contact shape carries tags rather than an explicit area, so grouping
uses the first tag. That is recorded in a comment at the call site.

Verified: 13 contacts tests, 13 store tests; typecheck, lint,
design-system contract, type scale and icon scale all clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
…ealth-n17dvm

# Conflicts:
#	data/repo-awareness-snapshot.json
@BigSimmo BigSimmo changed the title On Call mode — design spec (implementation to follow) On Call mode — a junior doctor's shift hub (implementation in progress) Sep 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e7c0227d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/consolidated-mode-home-redirect.ts
Comment thread supabase/schema.sql
Comment thread src/components/mode-nav/registry-mode-nav.tsx Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T11:40:47.529746Z 7e7c022 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Sections. Playbook, Referrals, Orientation, Teaching and Logistics, all
reusing the row and freshness primitives rather than growing five
variants.

The Playbook carries the mode's one real safety rule and now has a test
holding it: escalation steps are administrative facts the owner typed —
who to call, when, on what number — and every clinical statement is a
link to one of the owner's own documents, shown with its title and
date. A scenario with no linked guideline renders an empty state
pointing at a document search, and the test asserts no clinical
instruction language appears on that card. A page that fills that gap
would be worse than one that admits it.

Referrals labels what a service accepts and excludes as text rather
than colour-coded chips alone. Orientation attributes a pinned summary
to the owner, above the real document, so it never reads as the
manual's words. Teaching orders by next occurrence and marks a
recording link as leaving the app.

Editing. A sheet with per-section fields, validation surfaced on the
field, deletion guarded by a dialog that names the entry rather than
saying "Confirm", and a one-tap "still correct" action. Saving sends
the complete entry, which is what the API now requires.

Build fix. `updateOnCallEntrySchema` moves out of the route file into
`src/lib/on-call/api-schemas.ts`. A Next.js route module may export only
the framework's own names, and the extra export failed CI's production
build with TS2344. The comment claiming it was inert was wrong. Worth
recording why it got through: `tsc --noEmit` passes it happily and only
a real build catches it, so a "typecheck clean" report is weaker
evidence than it sounds.

Also adds the On Call row to the navigation density coverage. Deferring
that did not merely owe evidence later — it failed three assertions
immediately, which I should have run rather than assumed.

Verified: 88 tests across the editor, sections, routes, API contract
and navigation contract; a clean production build listing all ten
routes and three endpoints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
…docs

Search (`/on-call/search`) runs entirely over the browser cache, so it works
with no signal — the same constraint the sections were built to. It mounts
`SearchResultsHeaderBand`, which the mode's `resultsSurface: "results-band"`
declaration requires: without it a failed search reports "0 matches" instead
of saying the search itself failed.

The printable card (`/on-call/card`) is the paper fallback for a phone with a
flat battery. `card-selection` decides what reaches it: entries marked
on-card, excluding personal entries and — deliberately — excluding stale ones.
A number nobody has confirmed in twelve months should not be carried into a
night shift on a piece of paper that cannot show it is stale.

Docs and counts follow the seventeenth mode: the codebase index gains a real
On Call section (modules, routes, storage model, and the explicit statement
that the mode carries no app-authored clinical content), and the mode counts
in CLAUDE.md and the index move to 17. Two of the count strings became
count-free instead, which is how the 15-versus-16 drift recorded in the
2026-09-02 audit happened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
…ealth-n17dvm

The only conflict was data/repo-awareness-snapshot.json, which is generated:
regenerated with `npm run snapshot:repo-awareness` rather than hand-resolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
Four committed contract tests went red. None was a flake; each names a
repository rule the new mode had stepped outside of.

- The essentials card mounted `InPageNavHeader` itself. Every mode registers
  its claim on the phone header's addon slot in one `*-nav-header.tsx`
  sibling, so the pages competing for the single collapse owner can be read
  off a list of files rather than found by grep. The card's header is now
  `OnCallCardNavHeader`, beside the section header.
- `on_call_entries` was missing from the tenancy guard's explicit list of
  owner-scoped tables reachable from API routes. That list is the reviewable
  record of the tenant boundary; a new owner-scoped table has to appear in it.
- Two routes built `{ error, issues }` envelopes by hand. This repository has
  one public error envelope (`apiErrorPayloadSchema`), so the raw Zod issue
  list now stays server-side and the two distinct messages carry which half of
  the contract failed.
- The shared-composer routing test still asserted "On Call has no search route
  by design" — true when it was written, before `/on-call/search` existed.
  The code was right and the expectation was stale.

Three further suites (privacy readiness, clinical hazard controls, RAG plan
parity) were red only because this container held a shallow 114-commit clone
and they resolve real commit SHAs. They pass unchanged once the clone has full
history; nothing in them needed fixing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
A fresh reviewer read Tasks 12 and 13 against their briefs. These are its
findings, in the order they matter.

Search showed phone numbers with no freshness marker, while every section row
carries one. That made search the single surface where a number nobody has
confirmed in over a year looked current — the exact failure the whole
freshness system exists to prevent. Result rows now carry the same badge.

Search matched case-sensitively. Every haystack handed to `rankCatalogRecords`
must already be normalised, because matching runs against lower-cased query
terms; ours were raw. Orientation, whose content lives in `body` alone, was
effectively unsearchable. Mutation-tested: two of the three new cases go red
when the normalisation is removed. The third asserts ordering, because a cased
title is still found by the fuzzy fallback — being found is not proof, ranking
first is.

The count line read "3 on Call": with no `resultNoun`, the band lower-cases the
mode's label and calls it the noun. The existing test asserted only the digit
and sailed past it, so the new one asserts the phrase.

An owner with nothing added yet was told to "check the spelling" — blaming
them for a typo they did not make, and hiding the one action that helps.
Nothing to search is not a failed search.

The printable card said "No number on file" against playbook steps and
orientation notes, which have no number by design; it now prints the body,
which is their content. It also asserted "nothing is flagged" while the first
fetch was still running, and re-read the clock on every render.

The card's only entry point disappeared whenever Contacts was empty, stranding
a page that can hold flagged referral and logistics entries.

The filter sheet printed its own result count even when the search had
faulted, reintroducing one layer down the false "0 matches" the band exists to
suppress.

Task 13 shipped with no component test: nothing proved a stale entry stays off
the printed page. Nine cases now do, including never-verified and the exact
twelve-month boundary.

Two fixtures authored clinical content — a temperature threshold and an
escalation rule. This mode carries none, and a fixture is how such content
usually enters a repository: it gets copied into a seed, then a demo, then a
screenshot. They now carry the search term and no clinical claim.

Also removed: two unreachable `default:` arms (a seventh section should be a
compile error, not silently unsearchable), an unread route parameter and a
needless cast.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_86b717f8-e604-4eea-b232-bb52dacc8c17)

@BigSimmo BigSimmo changed the title On Call mode — a junior doctor's shift hub (implementation in progress) On Call mode — a junior doctor's shift hub Sep 4, 2026
…ealth-n17dvm

Three conflicts. Two were generated files (data/repo-awareness-snapshot.json,
docs/site-map.md) and were regenerated with the repo's own tooling rather than
hand-resolved.

The third was the mode-homes paragraph in docs/codebase-index.md, which #2614
rewrote on main to consolidate Documents and Medications onto the shared home.
Main's rewrite is the newer structure, so it wins; the On Call facts are
re-applied on top of it — seventeen modes, and /on-call as a thirteenth
consolidated redirect stub whose section pages live one level down.

Full offline suite on the merged tree: 1 failed, 15,126 passed. The one
failure is the stale drift manifest, unchanged and still blocking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
The whole-branch review found that only Contacts was wired to the entry store.
The other five list components — Playbook, Referrals, Orientation, Teaching,
Logistics — were imported by their own tests and by nothing else in `src/`.
Every one of those five pages said "no entries yet" no matter what the owner
had saved, offered no way to add anything, and the Playbook's "no local
guideline linked" state, which exists so the app never substitutes guidance it
cannot source, was unreachable. A playbook entry would appear in search and be
absent from its own page.

Each section id now resolves to its list component through one switch with no
`default` arm, so a seventh section is a compile error rather than another
silently blank page — which is exactly how this went unnoticed. The editor,
whose field map was already keyed by section, is mounted for all six, and the
five gain a per-row edit control and the same "still correct today" action
Contacts has. `useOnCallLinkedDocuments` resolves the ids the Playbook and
Orientation link to; a failed fetch yields an empty map, so those sections say
"no local guideline linked" rather than claiming one exists.

Five further defects, each found by the same review:

An account switch left the previous user's entries cached. Sign-out cleared
them; switching accounts did not, and a shared ward computer switches accounts
without ever signing out — so user A's ward numbers and personal lines rendered
for user B.

`onCallEntryFreshness` failed open on a date it could not parse: every
comparison against NaN is false, so the overdue branch fell through and the
entry reported as freshly checked, which put it on the printed card. A date we
cannot read is not evidence anyone checked it; it now fails to stale.

A browser blocking site data reported an empty hub after a completely
successful fetch, because entries were only ever read back out of localStorage.
The fetch result is now held in memory as the fallback for that browser.

A stale referral showed no marker until someone expanded its disclosure — the
one surface in the mode where staleness was hidden by default. The badge moved
to the collapsed header.

Three cards hand-rolled the shared `cardSurface` recipe and dropped
`forced-colors:border`, so in Windows High Contrast their edges disappeared.

Also: `createOnCallEntrySchema` was declared twice, so create and update could
have drifted apart; the last test fixture authoring clinical content (a
scenario stating a trigger and an escalation rule) is now content-free, like
the search fixtures; search no longer claims the owner has nothing while the
first fetch is still running; and `OnCallLinkedDocument` moved into the domain
layer, because `src/lib` may not import from `src/components`.

Full offline suite: 1 failed, 15,141 passed. The one failure is the stale drift
manifest, unchanged. Production build exits 0 with all nine On Call pages and
three endpoints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
CI's "Static PR checks" caught this: the committed snapshot was behind the
repository. The merge from main resolved this generated file by regenerating
it, but the regeneration ran before the later documentation edits, and the
merge left duplicate entries for docs/codebase-index.md and docs/site-map.md
in the process.

Regenerated with `npm run snapshot:repo-awareness`, which drops the duplicates
and re-pins the captured revision. `check:repo-awareness-snapshot`, along with
sitemap:check, docs:check-index, docs:check-links, check:design-system-adoption
and check:outstanding-issues, all pass locally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
CI's Lighthouse budget failed the home page: desktop LCP 1004 ms against a
786 ms baseline, +27.8%.

That baseline was last refreshed at #2399 and the Lighthouse job is
path-scoped, so it had been skipped on every recent push to main — main's
green said nothing about it, and the change could not be attributed from CI
alone. Measured both trees instead, on this machine, same Chromium, same
byte-identical node_modules:

    main      desktop-root LCP 772 ms   route /  244.0 KiB gzip
    this PR   desktop-root LCP 940 ms   route /  265.3 KiB gzip

So it reproduced, and it was this branch's. Twenty-one kilobytes for a
seventeenth mode is not a seventeenth mode's worth of code.

The cause: `src/app/layout.tsx` mounts the Supabase auth provider on every
page, and that provider clears the On Call cache on sign-out and on an account
switch. It did so by importing `clearOnCallEntryCache` from `entry-store`,
which pulls in `entry-model` and its six per-section Zod schemas. Anyone
opening the home page downloaded the entire On Call domain model so that
signing out could remove one localStorage key.

The key, its change event and the clear function now live in
`entry-cache-keys.ts`, which imports nothing. `entry-store` re-exports them, so
no call site changed.

    after     desktop-root LCP 873 ms   route /  244.2 KiB gzip

+0.2 KiB against main, which is what a seventeenth mode should cost.

`tests/on-call-root-bundle-isolation.test.ts` is the guard, because nothing
else would notice the import creeping back: it is one line, it typechecks, and
every other test passes. The test also pins the new module to zero imports —
anything added there is added to every page in the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_d9bcf7a2-a8fb-4491-8049-d2a1a7e136f2)

Two browser checks failed. Both were real, and together they exposed that
five of the mode's six pages could not be reached from anywhere in the app.

`ui-mode-nav-density` failed at all nine width points. Not because six labels
clipped — the diagnosis the spec and I had both prepared for — but because the
bar was never there to measure. Every On Call section route is an information
page (`isInformationPage`), so `PageSecondaryNavigation` returns null on all
six, and the destinations the mode registered in the shared header bar could
never be drawn. The registration described navigation the app does not render.

Removing it made the real defect visible: `route-reachability` immediately
named `/on-call/playbook`, `/referrals`, `/orientation`, `/education` and
`/logistics` as orphan routes. They always had been. The registry had been
satisfying that guard with links nothing ever painted, so a reader could open
Contacts and have no way to reach the Playbook except by typing the URL.

So On Call leaves the mode-nav registry — it is the ninth mode that registers
nothing — and gains an in-flow section strip in `OnCallSectionPage`: six links,
current one marked `aria-current="page"`, scrolling inside its own container so
the page never scrolls sideways. Deliberately in flow and not a second sticky
header: `OnCallNavHeader` is this mode's single phone-header collapse owner and
AGENTS.md forbids stacking another fixed bar under it.

The six links are written out one by one rather than mapped. The reachability
guard resolves `<Link href="/literal">` statically, so an interpolated path —
or one passed through a wrapper component's prop, which I tried first — leaves
the routes reading as orphans again. Verbose, but the verbosity is the point.

`ui-smoke`'s mode menu still counted 16 modes.

Verified: `ui-mode-nav-density` 57 passed, the mode-menu smoke test passed, and
the offline suite is 1 failed / 15,147 passed — the one failure being the stale
drift manifest, unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
The section strip lists its six links one by one, so the array it used to map
over is no longer read. The pre-push static guard caught it; my own eslint run
had not, because I checked before switching from the mapped form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC

BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

Production UI (1) on f6811fece — one failure, and it is not this PR's

The shard is 257 passed / 1 failed. The failure is tests/ui-sources.spec.ts:125 › Sources remains operable at phone width and under accessibility media, and the assertion is a strict-mode violation rather than a broken expectation:

Error: strict mode violation: getByTestId('sources-topics-main') resolved to 2 elements:
    1) <main data-testid="sources-topics-main" …> aka getByTestId('mobile-composer-reserve-pad').getByTestId('sources-topics-main')
    2) <main data-testid="sources-topics-main" …>.nth(1)

Two copies of the Sources topics page are mounted at once, one of them still inside mobile-composer-reserve-pad. The test does page.goto(route) and asserts on the next line (ui-sources.spec.ts:140), so it is racing the route transition: for a moment the outgoing and incoming shells coexist and the id is not unique. Timing, not behaviour.

Why it is not this PR's:

  • It passes on this exact branch head locally — npm run test:e2e -- tests/ui-sources.spec.ts --project=chromium -g "remains operable at phone width"1 passed (30.4s).
  • The diff touches nothing on that path. The one shared search-shell file it edits, src/lib/search-shell-props.ts, gains a single early return guarded by pathname.startsWith("/on-call"), which /sources/topics cannot enter. Sources' own components, the composer reserve pad and the shell transition are untouched.
  • The two failures in this job that were this PR's are fixed and green: the mode-nav density spec (57 passed) and the mode-menu smoke test.

Deliberately not re-running the job yet. The permitted single re-run buys nothing while Migration replay and Unit coverage are still red on the stale supabase/drift-manifest.json, which cannot be regenerated from this environment (see the thread on supabase/schema.sql) — the aggregate stays red either way. The commit that lands the manifest will re-exercise this spec naturally, and if it fails a second time it is real and I will treat it as such. Keeping the PR watched until then.

Worth flagging separately: the race looks pre-existing and general, not specific to Sources. Any spec that asserts on a main test id immediately after goto can hit it. That is a hardening item for its own change, not this one.


Generated by Claude Code

@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_e6abd411-e7ad-45f0-9726-70dc3e5ae505)

BigSimmo and others added 5 commits September 5, 2026 00:02
The final block of "app mode menu supports keyboard navigation without
removed prototype modes" focused the Answer option and pressed Tab without
waiting for the menu's own open-time autofocus to land, and without
asserting focus had arrived.

Opening the menu focuses the search box on a requestAnimationFrame. When
that frame lands after Playwright's .focus() call, Tab fires from the
search box instead of the option. Measured behaviour in that state: focus
moves to the first option, which is inside the mode-menu wrapper, so the
wrapper's onBlur never sees a focus-leave and the menu correctly stays
open. The assertion then times out ten seconds later with no indication of
why. On desktop only a Tab keydown on an option itself closes the menu.

Wait for the autofocus to settle, then prove the option holds focus before
pressing the key whose behaviour depends on it. The same test's first block
already guards both; only this block omitted them. Verified the search box
takes focus on reopen 6/6, so the settle-wait is a sound synchronisation
point rather than another race.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@BigSimmo
BigSimmo disabled auto-merge September 4, 2026 16:44
Resolves the conflict in tests/ui-smoke.spec.ts. Main independently reached
the same fix for the app mode menu Tab assertion - the same two focus
preconditions, the same reopenedAnswerMode binding - so only the explanatory
comment differed. Kept main's wording; the assertions are identical on both
sides.

supabase/drift-manifest.json is the operator-supplied regeneration from CI
run 33888898898, which replayed supabase/schema.sql on a Docker-capable
runner. Verified before landing: its schema_sha256 (a94a6471...) equals the
value tests/drift-detection.test.ts computes from this tree's schema.sql, and
its snapshot genuinely carries on_call_entries rather than only a bumped
hash. tests/drift-detection.test.ts now passes 21/21.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_6506d643-5e6a-4ce6-8a1b-c0b9d38a1aa9)

BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

Lighthouse budget on e09b41b6 — desktop / LCP regressed, and it arrived with main

| run          | LCP ms | baseline | TBT ms | baseline | CLS   | baseline |
| desktop-root |    962 |      786 |      8 |       29 | 0.000 |    0.007 |

**1 metric(s) regressed:** desktop-root lcpMs +177 (+22.5%) vs baseline (tolerance +20% and +100).

Not a sampling artefact: the gate saw the breach, collected two confirmations, and recorded confirmed regression; 2/3 samples breached. Every other cell is at or under baseline.

Why it is not this PR's. The immediately preceding head, dd80bdd1e, passed this same gate at 16:30 (run 33894989489). The only source change between that head and this one is the merge of origin/main at 0ee912748 — PR #2600, "Finish the PsychSift rename and re-cut the sidebar lockup as a header band". My own delta across those two heads is supabase/drift-manifest.json, which never reaches the browser, and a comment in tests/ui-smoke.spec.ts. Nothing I changed renders.

#2600 re-cuts the sidebar brand lockup into a painted header band, adding --brand-band-wash as a per-theme color-mix(in oklab, …) in globals.css plus ~99 lines of ClinicalSidebar.tsx. On desktop / that band is above the fold and in the LCP candidate region, which is the shape of a +177 ms LCP move on that route and no other.

The browser is constant across both runs (chromium-1234 in each), so this is not a pinned-browser change.

Why nobody caught it on main. lighthouse-budget is path-scoped and does not run on pushes to main, so #2600 merged without this gate ever grading it. This PR is the first branch to measure that tree. ci.yml already carries the note about the previous instance of exactly this — the baseline drifting ~163 ms on desktop /therapy-compass with nobody alerted.

Not spending the permitted re-run. The gate has already sampled three times and confirmed. A fourth sample would be hoping, not evidence.

No fix exists to port, and the remedy is a judgement call rather than a patch. Two options, and choosing between them belongs to the owner:

  1. Treat the 177 ms as a real cost and reduce it in the sidebar band — most likely by keeping the band's paint off the LCP path, rather than by reverting the design.
  2. Accept it as the intended cost of a deliberate design change and refresh the baseline. lighthouse-budget.json says refresh happens "only through the CI refresh artifact after an intentional, known-good run" — the refresh_lighthouse_baseline workflow dispatch, whose artifact a human commits. That is the same operator-shaped step the drift manifest needed.

I am not doing either inside this PR. Fixing another PR's design change here would widen an On Call PR into a performance change it was never meant to carry, and refreshing a baseline to absorb a regression is what #QSHHGK exists to prevent. Flagging it instead, with the attribution above, so it is decided deliberately and on main, where it belongs.

Keeping this PR watched.


Generated by Claude Code

@BigSimmo
BigSimmo enabled auto-merge September 4, 2026 17:07

BigSimmo commented Sep 4, 2026

Copy link
Copy Markdown
Owner Author

Correction: I was wrong to attribute the LCP regression to #2600

My previous comment blamed the desktop / LCP regression on #2600's sidebar brand band. I measured it, and that is not the cause. Retracting it before anyone spends time optimising a change that is not responsible.

The measurement. Two production builds, same machine, same browser binary, same measurement harness, differing only in whether #2600 is present:

tree desktop-root LCP FCP TBT LCP element phases
9ca3b4e95 — main before #2600 881.3 ms 333.8 12.0 h2#shared-home-empty-state-title TTFB 127 · Render Delay 754
e09b41b6 — this branch, with #2600 and On Call 881.5 ms 338.2 7.5 h2#shared-home-empty-state-title TTFB 129 · Render Delay 752

A 0.2 ms difference, the same LCP element, and the same phase split. #2600 costs nothing measurable on this route, and neither does On Call.

What the phase breakdown also shows: the LCP element on desktop / is the hero heading, and 85% of its time is render delay — 752 ms after a 338 ms first paint. That is main-thread work before the hero paints, and it is present identically in both trees. It has nothing to do with the sidebar band, which was the mechanism I proposed. I reasoned from "the band is above the fold on this route" to a cause without measuring it first, and the reasoning was wrong.

What the CI failure most likely is. The budget triggers only when a metric exceeds both +20% and +100 ms, so against a 786 ms baseline the trigger sits at ~943 ms. CI measured 962 ms — about 19 ms, or 2%, past the trigger. Earlier heads on this same branch passed the same gate. A route sitting that close to its own trigger will cross it on a slow runner without any code changing, and 2/3 samples breached is consistent with a slow measurement window rather than with a code delta — the three samples come from one runner in one window, so they are not independent of runner speed.

Caveat, stated rather than glossed: the local browser here is HeadlessChrome 141; CI pins 151, and cdn.playwright.dev is blocked from this environment so the pinned build cannot be installed. Absolute numbers therefore do not transfer between the two, and the gate correctly refused to grade my local run for exactly that reason. What does transfer is the relative comparison, because both trees were measured on the identical binary. A cost that only appears on 151 cannot be excluded outright — but the mechanism I proposed is absent on 141 at 0.2 ms, and nothing in the phase data points at the sidebar.

What I am doing. Re-running the failed job once — the single re-run this PR has not yet spent, now justified by evidence rather than hope.

Worth recording separately: desktop / sits ~7% under its own trigger on a good run. That fragility is not this PR's to fix, but it will keep failing unrelated PRs until either the baseline is refreshed on a known-good run or the hero's 750 ms render delay is reduced. That belongs on main.


Generated by Claude Code

@BigSimmo
BigSimmo merged commit d63790d into main Sep 4, 2026
62 of 64 checks passed
@BigSimmo
BigSimmo deleted the claude/on-call-mode-health-n17dvm branch September 4, 2026 17:29
BigSimmo pushed a commit that referenced this pull request Sep 5, 2026
… fifteen

SPEC §3 and DECISIONS §C5 both still said "fifteen modes". The registry in
src/lib/app-modes.ts declares seventeen: sources landed in #2510 and on-call
in #2608. The 2026-09-02 repository audit confirmed both strings (L81/L94) and
deliberately left them for the design-system owner rather than correcting them
in a documentation-only commit.

The claim these two lines make is unchanged and still holds — three
semantically-named identity families cover every mode, with no fourth family —
so only the count moves. No inbound link targets the C5 heading, and no test or
script pins either phrase.

Historical records keep their original numbers: the audit findings and the
superseded plans under docs/superpowers/ describe what was true when they were
written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LL9gE67N6yorjPcssuK4bC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants