Conversation
…7730) The Subnav is a nowrap flex row whose every zone sizes to its own content, so its min-content width is the sum of every label plus every padding. pano publishes five destinations plus a CTA, that sum is wider than a phone viewport, and a block in normal flow that wide grows the document's scroll width — which is the 500px the report measured inside 390px. Wrap the bar and its nested tab strip at the file's existing ≤640px breakpoint, following the Topbar's own phone rule. The sticky offset goes with it: `top: var(--topbar-h)` is only honest while the topbar is one row, and the topbar's ≤640px rule deliberately wraps search onto a second row, so below the breakpoint the bar rests in normal flow instead of carrying a height no stylesheet can read. Measured on a headless desk at 390/641/1280 against both Subnav.css revisions: pano's document scroll width drops 521px → 390px, every destination and the CTA stay on screen and unclipped, and 641px and above are byte-identical in bar height, position, offset and zone geometry.
🚀 Preview deployed
|
|
review-code: PASS @ ac023ad content:29fb74284131 — CI is green at this head and every code-class criterion is discharged; the rendered capture stays owed to review-ui Round 2, head What changed since round 1
Round 1's third finding (mecmua unmeasured) I re-read rather than inherited, and it does not CriteriaNone of the 8 rows carries an outside-diff evidence marker, so each is graded on the diff, read out
Note (non-blocking, and one for the rendered gate)The mobile subnav no longer sticks — it scrolls away with the feed below 640px. That is a real No acceptance criterion appended this round. Deviations
Verdict-written: 2026-09-21T01:06:32Z Superseded verdict — 2026-09-21review-code: FAIL @ ac023ad content:29fb74284131 — CI is red at this head: the blocking e2e job's auth setup fixture failed on both attempts, and unit + client tests never concluded Round 1, head The change itself reads right. It fails on its execution evidence: CI is red at this head. Findings1 — blocking — CI is red at the scoped head.
On inspection the failing fixture is the 2 — blocking (same head, unread) — 3 — non-blocking — a fourth CriteriaNone of the 8 rows carries an outside-diff evidence marker, so each is graded on the diff, read out
Deviations
Verdict-written: 2026-09-21T00:50:51Z |
|
Driver note — repair round 1 was a CI re-run, not a code change. The round-1 I re-ran the failed jobs of run 35548148138 at the same head ( Recorded as the repair round's Counting it as a hand-fixed gate rather than a silent retry: the lane spent a repair round on a flake, which is #9591's cost to price. |
review-ui: CANT-SEE at
|
| surface | viewport | outcome |
|---|---|---|
/pano |
mobile | captured 390x1340 — no exit-19 width refusal, no horizontal overflow; the bar holds sıcak / yeni / en iyi / tartışma plus the 14 başlık crumb on one line, crumb on the trailing edge |
/pano |
desktop | captured 1280x971 — single sticky row, same zone order as before |
/sozluk |
mobile | captured 390x844 — the shared bar wraps: three letter lines, the + yeni tanım CTA on its own line and still pinned to the trailing edge, bar resting in normal flow, no horizontal scroll |
/sozluk |
desktop | captured 1280x800 — single row, unchanged |
/divan |
mobile + desktop | captured, but the page is the yazar/moderator gate's error screen, so no Subnav rendered — divan's bar is unjudged at both widths (2 console errors, both the expected FateRequestError permission message) |
So the reflow rules in the diff did paint, and they read clean against the manifest's prose law on
the sözlük bar, which is the heavier case (26 letters plus a CTA). What is missing is pano's own
signed-in row and divan's bar — both behind login.
What the next runner needs
A shell holding the preview worker's real BETTER_AUTH_SECRET (ci-credentials export, behind
ALCHEMY_PASSWORD) and both seeded tier tokens for this preview's D1, then:
review-ui render --pr 9586 --out judged --surface /pano:auth --surface /pano:auth-caylak \
--surface /divan:auth --viewport mobile --viewport desktop --auth-secret-from <export>
Nothing in the diff is being asked to change on this note. The branch's text gate stands PASS at
this head; this is an evidence gap in the gate, not a defect in the PR.
LAW-SOURCE: manifest-prose — the repo declares no design-prohibitions.json.
Driver note — lane 7730 leaves its park for a repair round, not a dischargeThe recorded park cause is not discharged, and this comment does not claim it is. Lane 7730 sits at What changed is the spec, under a landed verdict. The founder ruled the one design question this PR raised: #7730 (comment)
So this PR's So what is owed at this head is a repair round. The driver is recording, in this order:
No reviewer verdict is being asserted, overwritten or faked by this. The Precedent for a driver-originated What the repair must hold (the ruling's own terms, now on #7730):
|
|
Round 3, head What I changed. The subnav sticks at every width now, offset by the topbar's measured height. Where to look first. One deliberate widening, disclosed in the body. The Evidence state, stated plainly. No The standing — at 41ae96f |
|
review-code: PASS @ 41ae96f content:25b68d6097b9 — merge-ready Round 3 at head CI at this head: Per-criterion
Standing checksTest honesty. One pre-existing assertion was replaced rather than deleted: Comment discipline. The comments are long, and I looked hard at whether they are narration. They Staleness traps. The measured height is cached in a CSS custom property, which is the shape this Release containment. No new surface. This is a layout fix to existing chrome, so there is nothing Silent failure, type design, test gaps. Portability guard. Not owed: the diff touches neither Deviations
Every entry matches something I found in the diff or the body, and I found nothing undisclosed. Terminal
Evidence for the marked criterionOne criterion on #7730 carries an outside-diff evidence marker, and here is what it rested on. Criterion: "Keyboard focus is never hidden under the sticky subnav: tabbing down the pano feed at Evidence named: "the rendered narrow-viewport captures in this PR's What I read. The rendered narrow-viewport captures do not exist at this head, and they are the Verdict-written: 2026-09-21T03:16:41Z |
review-ui: CANT-SEE at
|
| surface | viewport | width read back | sha256 (first 12) |
|---|---|---|---|
/pano (signed out) |
mobile | 390×1541 | 8312bc94f738 |
/pano (signed out) |
desktop | 1280×1320 | a1ebd7875d8e |
/sozluk (signed out) |
mobile | 390×844 | 49210d62c095 |
/divan (signed out) |
mobile | 390×844 | 94d3d3d86ef7 |
Zero page errors on the three that painted a page. Every capture is a full-page shot whose own
PNG header reads 390 wide, which is the document's scroll width — so the sideways scroll the issue
opened on (500px in a 390px window) is gone for the signed-out feed.
/sozluk at 390 is the strongest thing in this set, and it is worth naming because it is not the
surface anyone asked for. Its alphabet strip wraps to three lines inside a bar that grew to
contain it, and + yeni tanım sits alone on a fourth line pinned to the trailing edge. That is
the wrap plus the margin-left: auto CTA rule working on the shared bar, under a wrapped topbar,
at the phone width — 24-plus items, a harder case than pano's six.
/pano at 1280 is a single-row bar, same zone order, 20 başlık at the trailing edge. Nothing in
the desktop shot reads changed.
Advisory, not blocking, and not introduced here: the wrapped alphabet strip's rows sit at roughly a
24px vertical pitch, under the manifest's 36px tap-target floor (pillar 4, whose own note already
routes the concrete fixes to #2166). The letters were under the floor before this change too; the
wrap makes the rows denser rather than the targets smaller. Recording it, not blocking on it.
What did not render, in two kinds
Kind one — the signed-in bar. /pano:auth and /pano:auth-caylak refused on 11 at both
viewports: the preview answered the seeded cookie as a visitor. I narrowed that further than the
standing gap records do. I provisioned both tiers on this PR's own preview D1 through the
sanctioned path (preview-seed test-account, idempotent, refuses any database Cloudflare does not
name as a per-PR preview) — it returned ok, @onizleme-mod at yazar and @onizleme-caylak at
çaylak, sessions valid to 2026-09-28 — and re-ran the render. Still a visitor. So the account half
is done, and the one missing input is the signing secret: the ambient BETTER_AUTH_SECRET here
is 64 chars and not the insecure_ placeholder, but it is not the value the preview worker
verifies with, and the ci-credentials export needs an $ALCHEMY_PASSWORD this shell does not hold.
So the five signed-in destinations plus PanoSubnavCta at 390 — criterion 2, the signed-in half of
criterion 1, and criterion 7 verbatim — stay UNKNOWN. That row is the whole reason the issue
exists, and the issue's own triage note warned against settling it by reading the CSS instead of
loading the page. I will not repeat that error in the other direction and call it a PASS because
the mechanism looks right.
/divan at 390 painted its yazar-gated error screen rather than a Subnav, so divan's half of
criterion 6 is UNKNOWN too. Sözlük's half is PASS.
Kind two — the behavioural half, which no capture can answer at any tier. This is the part I
want on the record, because closing the auth gap would not fix it. Eight of this issue's sixteen
criteria are about what the bar does, not how it composes:
- stays pinned, flush under the wrapped topbar, while scrolling (criteria 4, 9, and the
no-gap/no-overlap row) - the offset re-measures after a resize across 640px in both directions, browser zoom, and a
root font-size change (criterion 12) - keyboard focus and in-page anchor targets clear the bar after tabbing (criterion 14, WCAG 2.4.11)
- 320px reflow (criterion 15)
review-ui render takes still captures at scroll 0, and its --viewport set is closed to 390 and
1280. It cannot scroll, resize, zoom, or tab, and it cannot shoot 320px at all. This session's tool
surface carries no claude-in-chrome, so there is no second pair of eyes here either. The PR's
hand-verification table answers all of it and answers it well, but that table is builder-authored
evidence this gate does not consume as its own.
The exact commands owed
One for the gate, once someone hands this shell the deployed secret. The accounts are already
seeded, so nothing else is needed:
node packages/fabrika-cli/src/bin.ts review-ui render --pr 9586 --out auth \
--surface /pano:auth --surface /pano:auth-caylak \
--viewport mobile --viewport desktop \
--auth-secret-from <a file holding the BETTER_AUTH_SECRET the preview worker deploys with,
exported from the ci-credentials stack's alchemy state behind $ALCHEMY_PASSWORD>
And one thing that is not a command, because no verb takes it: a human at
https://phoenix-phoenix-pr-9586-nj7i4vzuxm5szohj.kampusinfra.workers.dev/pano, signed in, at a
390px and a 320px window — scroll the feed and watch the bar stay flush under the wrapped topbar,
drag the window across 640px in both directions, zoom, then shift-tab back up the feed and confirm
nothing comes to rest under the bar.
Why this is not a PASS and not a FAIL
Nothing I rendered is broken — no crashed surface, no undisclosed hole (the PR's Deviations
disclose the scope narrowing), no blocking law row tripped, and CI is green at this head including
the design-token seam and the a11y gate. So there is no FAIL ground, and I will not mint one out of
an absence.
But an unseen input blocks PASS, and here the unseen input is the composition the issue was filed
about plus every behaviour the founder's ruling added. Grounding a PASS on the signed-out shots
would be inference dressed as a render: correct-looking CSS reasoning is exactly what said this bar
was fine before someone measured 500px. The gate that cannot see does not get to emit a plausible
verdict, so it emits none.
To the question put to me directly: no, it is not enough. Not because the evidence is weak — the
sözlük capture is real proof the wrap works on this bar — but because the two things this round
turns on are both outside it. One is a surface I cannot authenticate to. The other is a class of
behaviour this gate has no instrument for, at any tier, on any PR. The first is a credential away.
The second is a gap in the gate itself and I am filing it as one.
/panolaid out 500px wide inside a 390px viewport, so a phone got horizontal scroll on theproduct's main feed. The width came from the shared Subnav:
.kp-subnavis a nowrap flex rowwhose every zone sizes to its own content, so the row's min-content width is the sum of every
label plus every padding. pano publishes five destinations plus a CTA, that sum is wider than a
phone viewport, and a block in normal flow that wide grows the document's scroll width.
The reflow that fixes that is the sibling chrome row's idiom (
Topbar.css's own≤640pxrule),landed in
Subnav.css's existing@media (max-width: 640px)block: the bar and its nested tabstrip wrap, height goes
autoover amin-heightfloor, and gap and padding tighten. That halfis unchanged and stays.
The subnav stays sticky on phones
Round 1 of this PR rested the bar in normal flow below 640px, on the reasoning that its correct
sticky offset is a height no stylesheet can read. The founder ruled against that
(ruling): the bar stays
sticky at every width, and the offset follows the topbar's real height. This round does that, and
the
position: staticis gone.Nothing in CSS can read another element's height, so the two bars publish their own.
stickyChrome.tsgives each of them a ref that measures its border box and writes it to thedocument root —
--kp-topbar-measured-h,--kp-subnav-measured-h— behind aResizeObserver, sothe value re-publishes on every change to the box rather than being read once at load. That one
mechanism covers the wrap at 640px, a viewport resize in either direction, text zoom and a changed
root font size without enumerating any of them. Three rules consume it:
.kp-subnav'stopisvar(--kp-topbar-measured-h)— measured, at every width.html'sscroll-padding-topis the two measured heights added, so a focused element or anin-page anchor target never comes to rest under the bars (WCAG 2.4.11).
stickyChrome.cssdeclares what both properties resolve to before the first measurement andafter a bar unmounts: the topbar's own one-row token, and nothing at all for an absent subnav.
Neither default is a guess at a wrapped bar's height, and no rule in the change hard-codes one.
getBoundingClientRectrather thanoffsetHeightbecause the measurement is fractional — thetopbar reads 105.796875px at 320px, and a value rounded to a whole pixel leaves a hairline of
topbar background showing between the bars.
LAW-SOURCE: manifest-prose — this repo declares no
design-prohibitions.json, so the manifest'sprose prohibitions are the law this was built to. The diff adds no color, no type and no raw
spacing value: every declaration is a layout keyword, an existing role token, or a measured
runtime property.
Hand verification
ui rendercannot stand/panoup in this checkout: thewebsurface row starts the workerthrough
alchemy dev, whose plan fails withUnauthorized: Authentication erroragainstCloudflare here. That is exit 11 — UNKNOWN, not a proven render outcome. So the geometry was
verified by hand instead, on a desk running the app's own vite dev server and driving real Chromium
over
/panoat four viewports, reading the numbers back off the rendered page. Two things aboutthat desk are synthetic and nothing else is: the feed is empty because the API is down, and the
driver appends a tall block of focusable rows inside
<main>so the document scrolls and has tabstops below the chrome. The bars, their stylesheets and the measurement are the real ones.
topresolves toscroll-padding-topsticky105.797px145.797pxsticky101px141pxsticky38px70pxsticky38px70pxtopequals the measured topbar height at every row, and the gap is exactly zero — the bars areflush, with no overlap and no strip of topbar background between them. After scrolling 900px the
subnav is still pinned with the same zero gap at all four widths, so it no longer scrolls away with
the feed. Desktop is untouched: 641px and 1280px read the same 38px offset and 32px bar height they
read before the change.
The focus half was measured against the same desk, and it is worth stating what the comparison
shows, because the defect is pre-existing rather than introduced here. Tabbing backward up the
feed is the direction that scrolls a stop to the viewport's top edge, where the bars are. On
origin/main— sticky bar, noscroll-padding-top— focus landed attop: 46.8pxand thentop: -0.2pxagainst a bar whose bottom edge is at 70px: hidden under the chrome, a WCAG 2.4.11failure at every viewport including desktop. An in-page anchor landed at
-0.2px, the same way.With this change, across all four viewports, the lowest a backward tab stop came to rest is
178.8pxat 390px against a bar bottom of141px, and the anchor target lands at140.8pxagainst the same
141px— clear of the bar in every case, and nothing is hidden.Unit tests hold both halves in source, matching how
Subnav.css's existing rules are held: thereflow is inside the one existing breakpoint (a second
@mediafails the assertion), both rowswrap, and the narrow block declares neither
positionnortopwhile the base rule sticks offthe measured property above it.
stickyChrome.test.tsxdrives the hook against a firingResizeObserver— it publishes the rendered height on mount, each bar to its own property,re-publishes when the box changes, and withdraws the property on unmount — and pins the two
stylesheet rules the offsets depend on.
apps/webclient tests forsrc/components/layoutaregreen (69 tests) and
build check --surface codeis green.Two criteria stay owed to the design gate rather than answered here: the
review-ui render --viewport mobilecaptures of/pano:authand/pano:auth-caylak, which needthis PR's preview deployment, and the signed-in bar's five destinations plus the CTA, which the
signed-out desk does not render. Both are measured against a preview this lane cannot deploy.
Deviations
/panoas the check that it worked. Did: attached noui rendercaptures; the verb exits 11because the local worker's
alchemy devplan fails Cloudflare auth in this checkout, andreview-ui renderneeds a deployed preview that does not exist until this PR's own gate runs.Why: exit 11 is UNKNOWN, so recording it as a render would be claiming a look nobody took.
Disposition: the geometry proof is the hand-verification desk above, driving real Chromium
over the real app; the preview capture is the design gate's to take.
names the five signed-in destinations plus the CTA. Did: verified the signed-out bar only.
Why: the desk has no working API, so no session can be established on it. Disposition:
stated here; the reflow rules those criteria rest on are unchanged this round and were graded on
the diff in round 2, and the signed-in render is the design gate's at the preview.
apps/web/src/components/layout/Subnav.css.Did: also added
stickyChrome.tsandstickyChrome.cssbeside it and attached a ref inTopbar.tsxandSubnav.tsx. Why: the ruling requires a measured topbar height, and nostylesheet can read one — the measurement has to come from the component that renders the bar,
and
scroll-padding-topbelongs on the scrolling element, which ishtml. The reflow itself isstill entirely in
Subnav.css. Disposition: stated here and held by unit tests.sözlük's alphabet strip renders at
≤640px— it used to spill outside the 32px bar and acrossthe bar's own border, and the bar now grows to contain it. Why: the fix is on the shared
bar, and
height: autocannot be scoped to one consumer; the criteria ask for everySubnavShellconsumer to be checked on the shared bar. Disposition: stated here; it is astrict improvement, not a regression, and it is unchanged by this round.
scroll-padding-topthis change adds also fixes a pre-existing WCAG 2.4.11 failure at 641px andwider, where a backward tab stop or an anchor target landed under the sticky bar on
origin/main.Why: the offset is measured from the same two properties at every width, and scoping the
scroll padding to phones only would have left the desktop defect standing for no reason.
Disposition: fixed rather than left, measured in the section above.
Topbar.css:276-301is the idiom to follow, and it only dropsits spacer. Did: dropped the spacer and added
margin-left: autoon.kp-subnav__cta.Why: the Topbar has no trailing-edge primary action; the Subnav does (Decision: the placement law — where each nav element class lives #2587), and without
the auto margin the CTA left-jams against the signal on the wrapped line. Disposition:
stated here, and held by a unit test.
Did: added a
narrowBlockhelper inSubnav.test.tsxin an earlier round, and this roundreplaced that file's
rests the bar in normal flowassertion with its opposite — the base rulesticks off the measured property, and the narrow block declares neither
positionnortop.Why: the assertion the earlier round added pinned the behaviour the founder ruled against, so
leaving it would have red the suite against the ruling. Disposition: replaced, not deleted;
every other assertion in that file is untouched and still passes.
build check's green namesSubnav.cssandstickyChrome.cssinunvalidated. Did: ran no second surface for them. Why: thesurfaces are code, prose, plan and workflows, and none of them validates a
.cssfile beyond theguards the code run already swept over it (
design-token-guard,design-inventory).Disposition: stated here; CI's own gates answer it.
Fixes #7730