Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix the product's chrome: the reading tab, the call control, and the corner readout - #28

Closed
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format
Closed

Fix the product's chrome: the reading tab, the call control, and the corner readout#28
Darkest-Teddy wants to merge 7 commits into
mainfrom
worktree-fix-site-format

Conversation

@Darkest-Teddy

Copy link
Copy Markdown
Contributor

Fixes the reported breakage: the format being wrong, backgrounds missing, colours missing, the top navigation naming the wrong place, and the read feature being gone.

What was actually wrong

The read feature was gone, and it took the background with it. Pressing Read & mark threw atmosphere: unknown scene "read" out of a React effect. React unmounts a tree whose effect throws, so the whole product went blank — no chrome, no panels, no WebGL scene. Reproduced in the browser before the fix; the tree was empty (#root had zero children) and two page errors were logged. Fixed on main already by "Register every scene the package has, not five of the seven"; this branch merges that in.

Prose was sitting on a live WebGL field. The reading surface and the position form had no ground under them, so type ran straight over a moving scene. Also fixed on main ("Stand reading and answering on a plate"), merged here.

The header sat across the text on a long page. Fixed on main ("Take the header up on the way down the page"), merged here.

"Your call" had lost its styling entirely. New in this branch. The most consequential control in the product — three options, sealed on submission — rendered as one line of unstyled prose: AdvanceDo not advanceCannot conclude. It was written against .rail and .persona, two classes the dark rewrite dropped from app.css. Repaired with the .choice + button.ghost[aria-pressed] pattern the app already uses in two other places, so no CSS was added. Its dead htmlFor pointing at a div is replaced with a real role="group" / aria-labelledby.

The chrome named the wrong place. New in this branch. The corner readout said ARCHIVE over the Section and CULTURE over the Helix, and the menu lit Dashboard on a case's Record tab. The corner took its name from whichever menu entry was lit, and reading and the record have no menu entry. Codenames are now keyed by scene id and read through codenameFor(route), so the corner asks the same function the backdrop asks.

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npx vitest run — 860 passed, 63 files
  • Driven in a real browser across dashboard → case → read → position → record: no page errors, shell and canvas alive on every route, corner reads Culture / Archive / Section / Archive / Helix, menu stays on Library through every stage of an open case.

Note on branches

This branch is the union of main and feat/product-in-the-atmosphere. The three fixes above already on main were never on feat/product-in-the-atmosphere, which is what was checked out and running.

🤖 Generated with Claude Code

Darkest-Teddyand others added 7 commits August 16, 2026 18:57
A server was running against this repo for hours answering source:"stub"
while a working key sat in .env. It was started from a second checkout - a
worktree under .claude/worktrees - which had neither the key support nor an env
file. Nothing in the banner could distinguish that from having no credentials,
because the banner never said which directory it resolved configuration
against. It does now.
The 503 body stays {"error":"no_key"}: spec section 10 pins it and three tests
assert the exact object. So the actionable half goes to the banner instead,
where the person starting the service reads it - naming the variable to set
rather than restating that something is absent. Four credential mechanisms
reach Gemini and "no credentials" narrowed it to none of them.
That silence is the same one that let GEMINI_API_KEY sit in a .env doing
nothing before the key path existed. Worth closing twice.
Verified against a directory with no env file, with a project but no ADC, and
with .env.share alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I recommended it on a verification that used a simplified schema. The real ones
do not look like that. generativelanguage.googleapis.com rejects
`additionalProperties: false` outright -
Unknown name "additionalProperties" at 'generation_config.response_schema'
- and every schema in services/api sets it: adjudicate, ask, extract, interpret,
navigate. So a deployment on that host answers unconstrained calls happily,
prints LIVE, and fails on the first real adjudication.
That failure mode is the argument for naming the endpoint in the banner rather
than inferring "Vertex" from the model name, so the host stays reachable and is
documented as unusable instead of removed - a misconfiguration worth being able
to reproduce.
Also records the billing precondition. An API key on a project with no billing
account linked is free-tier only, 20 requests/minute shared across every holder,
and Vertex refuses it outright.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Library and Ask surfaces were empty for everyone but the machine that
downloaded the PDFs. A fresh clone showed "This document cannot be searched -
not in this checkout" on every entry, which reads as a broken product rather
than a deliberate exclusion. Ask is the surface being demonstrated; a
demonstration nobody else can run is not one.
The rule this reverses said the files were retrievable from accessdata.fda.gov
or ema.europa.eu "by the URL the spec records". No URL is recorded anywhere.
library-sources.json carries filenames and an NDA number, there is no fetch
script, and so retrieval meant hand-searching FDA's site for thirty-five
documents. The stated escape hatch did not exist.
363 MB, accepted deliberately rather than by omission. Largest single file is
40.9 MB, inside GitHub's limit. Git LFS is the obvious next move if this becomes
unwieldy - the files never change, which is exactly what LFS is for.
All sixteen library entries now resolve. Fourteen are askable; tak994 has no
source document, tolcapone is a scan with no extractable text, and troglitazone
has no nonclinical chapter heading - content refusals the product is supposed to
make, and a different fact from a missing file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Run-it section described accounts and configuration and said nothing about
the Library, which was the surface a fresh clone actually found broken. It also
now names the directory the banner prints, because a server started in a second
checkout reading that checkout's configuration is the failure this repository
produced twice in one day.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Your call" is the one irreversible control in the product - three options, sealed
on submission - and it was rendering as a run of unstyled prose:
AdvanceDo not advanceCannot conclude
The markup asked for `.rail` wrapping `.persona` buttons. app.css defines neither.
Both were real rules once, in the light stylesheet the product had before it went
dark; the rewrite dropped them and left the two class names behind in screens.tsx,
where nothing failed loudly because a class that matches no rule is not an error.
No border, no ground, no gap, no pressed state - three buttons the width of their
own text, touching.
REPAIRED WITH THE PATTERN THIS APP ALREADY HAS rather than with two new rules.
`.choice` wrapping `button.ghost[aria-pressed]` is what the finding editor and the
new case form both use for a mutually exclusive set, and it carries the pressed
state this control never had. No CSS was added - a fourth spelling of one control
is how the first three drifted apart.
`htmlFor="call"` went with them. A label's `for` names a form control and a div is
not one, so the attribute resolved to nothing and the group had no accessible name
at all. `role="group"` with `aria-labelledby` is the arrangement that actually
carries "Your call" to a screen reader.
The test asserts the pattern, not the class spelling, and separately asserts that
neither dead name has come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corner readout said ARCHIVE while the Section was drawing behind the reading
surface, and CULTURE while the Helix closed over a record. The menu, at the same
moment, lit DASHBOARD on a case's Record tab - disagreeing with the breadcrumb, the
case title and the stage strip all at once.
One cause. The chrome asks two different questions and had one answer between them:
which PART OF THE PRODUCT you are in (the menu highlight) and which PLACE you are
standing in (the corner). The corner read its name off whichever menu entry was lit,
and two of the six environments have no menu entry - reading and the record are
reached from inside a case, never from the menu - so both borrowed somebody else's
name and announced the wrong world.
This is the drift nav.ts opens by warning about, arriving from the side that note
did not expect. Not a stale table: a table that never covered the case. `codename`
was a field on the four menu entries, so it could only ever name four of six.
Keyed by SCENE ID now, which is what the backdrop mounts, and read through
`codenameFor(route)` -> `CODENAME[sceneFor(route)]`. The corner cannot say a
different word from the scene that mounted, because it is asking the same function
the backdrop asked. Adding a scene without a name is now the thing that shows up,
rather than a wrong name that does not.
The menu highlight stays a separate answer and is now complete: `record` was
missing from the case-route list and fell through to the Dashboard, the single
entry that is not where the reader is. Every stage of an open case lights the
Library, the record included - it is the last stage of a case, not another part of
the product.
Tests are written against the two questions rather than the table, so they still
mean something when the table is rewritten, and they check that every route the
router can produce has a name at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ee37025-bc28-461c-a05a-dec4addcb8ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

AndresL230 added a commit that referenced this pull request Aug 17, 2026
… read
Two fixes from #28, reimplemented against a main that has moved a long way
under that branch rather than cherry-picked from it.
THE CORNER READOUT. It read its name off whichever menu entry was lit, and
that is a different question from which place the reader is standing in.
Main had already fixed the half of this that reading suffered from - `read`
has a menu entry now - but the record still has none, so it borrowed the
Library's and the corner said ARCHIVE while the Helix closed over a sealed
record. Measured before touching it: currentNav answered `Archive` for
{ name: "record" } while sceneFor mounted `record`, whose name is `Helix`.
Keyed by scene id now and read through codenameFor(route) ->
CODENAME[sceneFor(route)], so the corner asks the same function the backdrop
asked and cannot say a different word. `codename` leaves NavItem: two
tables holding one fact is what let them disagree.
NOT read from the registry that owns those names, which is the obvious
simplification and a bundle regression. scenes/registry.ts statically
imports all seven scene factories and each imports three and gsap;
Backdrop.tsx reaches the package through `await import()` for exactly that
reason, and Chrome.tsx imports nav.ts eagerly. A static `import { STATES }`
would put the 3D stack in the chunk that draws the sign-in screen. The
duplication is paid for in nav.test.ts, which imports the registry for real
and fails if the two drift - a test can afford the import, the shell cannot.
THE CALL CONTROL'S NAME. `<label htmlFor="call">` pointed at `<div
id="call">`. A label's `for` names a labelable form control and a div is not
one, so the attribute resolved to nothing and the most consequential control
in the product reached a screen reader with no accessible name. Nothing
failed: an unmatched `for` is silent. role="group" with aria-labelledby is
what actually carries "Your call" across.
Seven tests, each checked against the broken state first: three fail on a
wrong codename, three on the old label markup. 1131 passed / 72 files with
Postgres and Storage up, typecheck 0, lint 0.
#28 stays open. Everything else on it is either already on main or carries
the two conflict traps recorded in docs/HANDOFF-open-prs.md.
Co-Authored-By: Darkest-Teddy <hel.jack14@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…was wrong
The two live fixes are on main. Neither was cherry-pickable - main had
independently fixed more than half of each, and both commits conflict now.
The correction worth keeping: this document told the next reader to drop
the local CODENAME table and read STATES from the atmosphere package
instead. That is a bundle regression. The registry statically imports seven
scene factories and each imports three and gsap, and nav.ts is in the
eagerly-loaded shell chunk. The duplication is deliberate; a test pays for
it. Advice that reads as obvious cleanup is exactly the kind worth writing
down as refused.
Also corrects the title, the count, and §1's claim that main and the
atmosphere branch are identical. They were, at 0ad996e. They are 17 apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…, three fixes
What this session actually did, and how each claim in it was checked.
#25 reviewed from scratch, which nothing had done - its entry was a placeholder that said
so. The ten numbers are sound: every headline re-derived from the raw rows rather than
trusted, Wilson checked against the standard interval, the Ask/retrieval same-fixture
cross-check confirmed real. Three defects in the instrument, all fixed: one headline was
read from a summary field in the file that promises it never does that, one cross-check
could not fire because the results file predates the field it keys off, and a product
change to extraction retrieval rode along unmeasured by any of the ten and untested.
#27 split; the good half is prepared. Also records a third blocker nobody had noted - it
reverts SHAPE_ASK from 64000 to 16000 - and corrects "four eval scripts" to six.
#28 verified empty against current main rather than asserted: five conflicts, all in files
main has superseded. Two corrections to this document's own account of it, both from the
same mistake - reading `git diff main PR` as if it were a merge. It is not: neither merge
base contains `responseSchemaFor`, so main ADDED it and a merge keeps it.
Corrects the no-database baseline, which was wrong by 7. 1055 + 76 = 1131 is the Postgres
total; the recorded 1048 + 76 = 1124 was #33's merge-commit figure carried into a row it
had stopped belonging to. New baselines at 1c25747 measured in both environments.
Corrects the Node-20 note: CI pins node-version 22, and the real item is the action
runtime. Records the dependabot triage, the two #24 risks now fixed, the withTransaction
fix, and the .gitignore hole that let a venv symlink be committed during this session.
Adds the two things this document keeps getting wrong: origin/main moves mid-session, and
the open-PR count must come from `gh pr list` rather than from a sentence - it has now
been miscounted twice in opposite directions, the second time omitting #34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Aug 17, 2026
…se the queue
`tools/seed-demo-documents.mjs --reset` deleted four paths under `results/` and printed
"Store cleared". Since #33 the backing is chosen by DATABASE_URL in one place, and when it
is set the record, accounts, invites and documents are in Postgres and Storage - so the
delete hit files nobody was reading and the success line was a lie told to the one person
who most needed the truth: whoever was about to re-seed. They would seed on top of
everything still there, which is the duplicated-cases state the flag is usually reached for
in the first place.
It refuses now, rather than clearing Postgres itself. Deleting rows from a database this
script never opened, on a URL it cannot verify is a demonstration deployment rather than a
live one, is a worse failure than not doing it - and the log is hash-chained, so a partial
delete is not something the product can be talked out of noticing later. It names the two
migrations to re-apply and the bucket to empty instead.
The guard is at the TOP of the file, above the fetch and the PyMuPDF gate. That placement
is the substance of the fix, not tidiness: a Postgres host is the machine least likely to
have 363 MB of PDFs or PyMuPDF on it, so the same check below the gate is unreachable from
exactly the deployment that needs it - the run dies on a missing extractor instead. Checked
both ways: with DATABASE_URL set it refuses and exits 1 having touched nothing; unset, it
falls through to the ordinary file path.
A tracked `.env.defaults` was considered here and REJECTED on second look, so no
`.gitignore` negation was added. The mechanism already landed - env.ts reads the file when
present - but there is nothing safe to put in it. #27's proposed models are not the ones the
scoreboard was measured on and the six bare `loadEnv()` eval scripts would inherit them;
`gemini-flash-latest` is a floating alias, worse again for reproducing a committed number.
The models that ARE agreed are already in git as DEFAULT_ADJUDICATION_MODEL and
DEFAULT_SHORT_MODEL with their measurement written out beside them, and a tracked file
restating them is a second place for one fact to live. `.env.*` stays a blanket.
Records the close of #27 and #28 and the deletion of feat/product-in-the-atmosphere, and
what closing #27 gives up that is worth rebuilding: a fresh clone has the case content in
git but nothing open, so the product looks empty and the data looks unshared.
1205 passed / 95 skipped without a database, 1300 passed / 0 skipped on Postgres and
Storage - unchanged, as a tools-only change should be.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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

@Darkest-Teddy@AndresL230