Files
civitai__civitai/docs
Zachary Lowden 2803e825b1 feat(app-blocks): give sensei a full-bleed exemption from the page-width cap (#4804)
* feat(app-blocks): give `sensei` a full-bleed exemption from the page-width cap

Adds `sensei` as the second member of the FULL-BLEED OPT-OUT LEDGER in
`src/styles/globals.css`, keyed on `[data-app-page-frame][data-block-id='sensei']`
— the same selector shape the `playable-collections` rule uses after its
production fix, NOT the `data-testid` spelling that `next.config.mjs` compiles out
of the live DOM.

This is a PRODUCT DECISION by the repo owner, not a bug fix. Nothing about the cap
is malfunctioning for this app; sensei is one of the two apps the cap's own census
was written about, and the trade is being taken the other way for it. Notepad, the
sibling case in that same census line, is deliberately NOT changed here.

The membership assertion was watched fail before it was updated: with the rule in
and the expectation untouched, `pageBlockHostMaxWidth.test.ts` went red naming the
set delta (`+ "sensei"`, expected `['playable-collections']`), which is the
designed workflow for this ledger.

Also corrects the ledger's membership claim in every place it was restated, which
turned out to be three rather than two:

  · `PageBlockHost.tsx` still asserted "NO LEDGER ENTRY IS WRITTEN TODAY, and the
    ledger's expected set … is `[]`". The test's real expectation was already
    `['playable-collections']`, so the comment was the stale side and had been
    false since that entry landed. It no longer restates the membership at all.
  · `globals.css`'s ledger header said "One member today". It now names no count —
    the rules below it are the authority.
  · `PageBlockHostMaxWidth.browser.test.tsx` said "THE LEDGER'S ONE REAL MEMBER"
    and named `playable-collections` in its own title. Its green arm is now DERIVED
    from the rules it already parses out of `globals.css`, so every member is
    measured and no count is stated; the enumeration that must fail on growth AND
    shrink stays in the node tier, which is a different claim.

The publisher-facing HOW-TO in `docs/features/app-blocks.md` gains the entry with
its reason and likewise drops the count.

Verified (both vitest tiers read, per the two-tier rule):
  · node `unit` — `pageBlockHostMaxWidth.test.ts` + `ledgerSelectorSurvivesProdStrip.test.ts`
    16/16 green; red→green on the membership assertion shown above.
  · browser `component` — `PageBlockHostMaxWidth.browser.test.tsx` 11/11 green,
    and the new derived arm was mutation-tested: setting only the sensei rule's
    value to `1500px` failed with THIS arm's own message ("the app 'sensei' is NOT
    full-bleed", 1500 vs 2560) while the other 10 tests stayed green.
  · `pnpm run typecheck` 0 errors; `prettier:check` clean; eslint on the touched
    TS files 0 errors (1 pre-existing unrelated hooks warning); `test:lint-rules`
    510/510.

NOT verified: a green browser tier is specifically NOT evidence the selector works
on civitai.com — vitest never runs with `NODE_ENV=production`, so the
`reactRemoveProperties` strip never applies, which is exactly how the `data-testid`
spelling shipped broken with that suite passing. The production-safety claim rests
on `ledgerSelectorSurvivesProdStrip.test.ts` (which compares the two
configurations) and on mirroring the already-fixed rule's shape. A CSS rule's real
proof is a rendered page on a wide viewport, which was not reached.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKwVejr8dHqb8nydsfqHk3

* docs(app-blocks): make the full-bleed ledger's bar admit its second member by the rule

The ledger's admission criterion excluded the entry the same PR added. It read
"an app whose surface is the canvas … NOT for 'it looks bigger'", and the sensei
entry openly does not clear it — it says the cap binds exactly as designed and
the owner decided the trade should go the other way. A bar the second of two
members openly fails, with nothing amended, is decoration: the hazard is the
THIRD entry, which now has precedent for "the owner said so" as a ground the
written criterion still excludes.

So the criterion now names the category actually used. Two grounds, and an entry
must say which it claims: (1) the surface is the canvas, unchanged; (2) an
explicit product decision by the repo owner, admissible only if it records who
decided, what trade was accepted, and what it is worth as a measured
display-width class. The "NOT for 'it looks bigger'" exclusion is KEPT and is
what ground (2) is distinguished against — a preference has no author, no stated
cost and no measured reach, so the three bullets refuse it by the same sentence.
Ground (2) does not waive ground (1) generally; it admits the entry that shows
its cost, one at a time. The sensei entry then records those three things, with
the record kept to what is actually on it and no extrapolation of the owner's
reasoning.

The entry's other half was an unasserted cross-repo reading that disclaimed
itself — "no ref recorded, no fixture reproducing it … the sensei repo was not
re-read for this entry" — while member 1 one slot up carries a deployed ref and
five file:line citations. Same slot, opposite evidentiary standard. The reading
has now been taken and the census HOLDS, so the citations replace the
disclaimer, matching member 1's convention: root shell, main row, the fixed
240px sidebar, the chat pane, and the absence of any max-width on the transcript
path, each at its own file:line. Recorded as `trunk 6fd63c0` and explicitly
flagged as a BRANCH TIP rather than a resolved deployed ref, because none was
resolved for sensei — claiming a deployed ref that was never resolved is the
error this flag exists to prevent.

Two deletions, both reviewability:

  - `PageBlockHost.tsx` quoted the old false sentence verbatim, declared "THE
    MEMBERSHIP IS DELIBERATELY NOT RESTATED HERE", and then restated the
    membership five lines later. Both paragraphs collapse to one line; cutting
    the second is what makes the first's stated policy true. `git log -p` holds
    the old sentence and the enumeration test already fails on growth and shrink.
  - `docs/features/app-blocks.md` had swapped a rotting COUNT for a rotting
    LIST. Nothing asserts the doc's member list matches `globals.css` — the
    prod-strip guard pins the doc's selector SHAPE only, never its membership —
    so it reduces to one sentence pointing at the ledger, which the doc already
    calls the authority.

No selector and no assertion logic changed: comments, one docs paragraph and the
criterion only. Verified on both tiers in an installed worktree (Round 0 could
run neither). Unit — `scripts/test-unit-run.mjs`, 16 tests across
`pageBlockHostMaxWidth.test.ts` + `ledgerSelectorSurvivesProdStrip.test.ts`,
green before and after. Component — `scripts/test-component-run.mjs`, 11 tests
in `PageBlockHostMaxWidth.browser.test.tsx`, green before and after, with the
membership and opt-out render arms unmoved. `typecheck` clean.

The generic prod-strip guard was confirmed to cover the new rule by mutation
rather than by reading: keying the sensei rule on `data-testid` turns exactly one
test red — "no shipped ledger rule depends on an attribute production strips" —
with that guard's own message and a payload naming the sensei selector, while the
membership assertion stays green. That guard came from PR #4590, not this PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKwVejr8dHqb8nydsfqHk3

* fix(app-blocks): pin the ledger walk against the file, and retract a criterion claim that does not hold

Round 1's fix round traded away a mutation class, and the ledger's admission
criterion claimed more than it can enforce. Four findings, no runtime change —
one test assertion, comments, and one docs word.

F1 — the derived green arm could miss a member without failing.
`ledgerFromGlobals` walks the CSSOM and descends into `CSSLayerBlockRule` only,
so a member rule nested in `@media`/`@supports`/`@container` drops out of
`ledger.ids`. The rewritten control only asserted the list was non-empty, and a
count cannot see a PARTIAL loss: the OTHER member keeps it non-empty, the loop
never mounts the lost one, and the test passes. Measured, not theorised —
wrapping the sensei rule in `@media (min-width: 3000px)` left this file 11/11
and the two node-tier guard files 16/16 while sensei rendered capped at 1600 on
a 2560 display. Neither of the other guards can see it: both read raw text,
where the id is still present.

So the relationship is pinned instead — the ids the CSSOM walk could REACH must
equal the ids the file textually contains, comments stripped. That keeps the
"no membership restated here" property (both sides derive from the shipped file)
and fails the moment a rule moves somewhere the walk cannot reach. Watched red
under the `@media` wrap at 1 failed / 10 by its own message, naming the delta
`- "sensei"`; green with the rule at the top level. The id regex is now shared
by both parses so the comparison can only ever be about reachability, never
about quoting.

F2 — the admission criterion does not refuse "it looks bigger", and said it did.
Ground (2)'s three bullets were claimed to exclude a bare preference because a
preference has "no author, no stated cost and no measured reach". That inference
does not hold: every request has a requester, WHAT TRADE is the same ~150px
gutter arithmetic for every app (a property of the 1600 cap, not of the app),
and WHAT IT IS WORTH measures the reach of the EXEMPTION, so two different apps
get a literally identical answer. The entry admitted under it proves the point —
it records that no app-specific justification exists and clears the bar anyway.

The strict repair would make sensei's own entry inadmissible, so the claim is
RETRACTED rather than replaced by a stronger one: ground (2) is an owner
override, the bullets make an entry recorded and reviewable rather than
justified, and the `NOT for "it looks bigger"` exclusion is moved onto ground
(1), where there is a claim about the surface to be wrong about. The shape to
watch for — a run of entries each copied from the last — is named, with the
honest alternative (move or drop the 1600 default) stated. The sensei entry now
says outright that it is not a template. Two sub-points with it: the WHO bullet
accepts a role, since this repo is world-readable and a role is what is actually
applied; and "this changes nothing at all for where the traffic is" is marked as
a claim about display WIDTHS, not about a population this repo measures nowhere.

F3 — the cap's value-justification still named Sensei as a case it governs.
After this branch Sensei gets the viewport, not ~1350px. The paragraph now
describes the two-pane SHAPE the value is justified against and names no app,
which is also the only phrasing that does not restate membership in a file that
deliberately refuses to.

F4 — docs: "the two grounds" is the count the same sentence says is not mirrored.

Re-verified unchanged after the F1 rewrite: the membership assertion still fails
in both directions (expectation reverted -> `+ "sensei"`; CSS rule deleted ->
`- "sensei"`, browser tier correctly still green there, since the shrink claim
is the node tier's), the derived arm's `1500px` mutation still dies with its own
message at 1 failed / 10, and the prod-strip mutation still isolates at
1 failed / 15.

node `unit` 16/16 (scripts/test-unit-run.mjs, the two guard files), browser
`component` 11/11 (scripts/test-component-run.mjs), scripts/typecheck.mjs 0
errors, prettier:check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKwVejr8dHqb8nydsfqHk3

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 13:56:28 -05:00
..
2025-11-19 13:39:55 -07:00
2026-04-21 17:28:15 -06:00
2026-03-27 11:25:47 -04:00
2026-03-13 17:31:05 -04:00
2026-04-24 15:34:42 -06:00
2026-06-11 10:33:01 -06:00
2026-06-11 10:33:01 -06:00
2026-01-27 14:25:06 -04:00
2026-03-23 19:49:29 -06:00
2026-01-27 14:25:06 -04:00