docs/solutions/architecture-patterns/a-layout-breakpoint-is-encoded-in-more-places-than-it-is-defined.md
Rama main · réplica de lectura del repositorio original.
---
title: A layout breakpoint is encoded in more places than it is defined
date: 2026-08-25
category: architecture-patterns
module: split-layout
problem_type: architecture_pattern
component: frontend
severity: high
applies_when:
- "Changing a viewport/layout threshold (a breakpoint, a min-width gate, a split-vs-stacked switch)"
- "Unifying two thresholds that previously differed per platform (web vs desktop app)"
- "Auditing why a class-applied CSS block duplicates a media query"
tags: [breakpoint, media-query, split-layout, e2e, css-drift, tauri]
---
# A layout breakpoint is encoded in more places than it is defined
## Context
Issue #6417 (implemented in PR #7165, open as of this writing) moved the dashboard's split-layout threshold from 1600px (web) / 900px (desktop) to a single 900px constant. The threshold turned out to be *encoded* in far more places than it was *defined*: nine TS/CSS literals, an entire duplicated CSS block, an e2e viewport table, and localStorage key semantics. Each encoding looked independent, and one of them (the 900/1600 split in `panels.css` vs `panel-layout.ts`) had already produced a shipped drift bug — #6426, where saved bottom-zone panels vanished for desktop windows in the 900–1599px band.
The expensive discoveries were the encodings that did not *mention* the number:
- **A class-applied CSS block duplicating a media query.** `.main-content.desktop-grid { ... }` re-stated the whole split layout so the desktop app could get it below the web's 1600px gate. Once the thresholds unified at 900px, the block became provably dead: the Tauri window minimum width (1200, `src-tauri/tauri.conf.json`) keeps the desktop app inside the `@media (min-width: 900px)` query at all times. The duplicate had also been *masking* an inconsistency — a zoomed desktop webview below 900 CSS px kept the class-driven split while the JS zone logic (`getEffectiveUltraWide()`) had already switched to stacked.
- **An e2e viewport table that encoded the breakpoint as behavior, not as a number.** `e2e/dashboard-news-request-budget.spec.ts` pinned viewport→scroll-owner pairs (`1280px → 'main-content'`). Nothing greps for "1600" there; the old threshold lived in the *expectation* that 1280px is a stacked layout. CI went red only after the change shipped to the PR.
## Guidance
When changing a layout threshold, sweep every **encoding**, not every **occurrence of the number**:
1. **Define it once** in a zero-import TS module (`src/app/split-layout.ts`, `SPLIT_LAYOUT_MIN_WIDTH`) and make every TS site reference it.
2. **CSS cannot import it** — media queries keep literals. Pin them with an alignment test that imports the constant and asserts the CSS text (`tests/responsive-zone-listener.test.mjs` asserts the `@media (min-width: 900px)` gate, the `(max-width: 899px)` hide, the grid-track floors, and that no `1600` survives in the runtime files).
3. **Audit class-applied CSS blocks that duplicate a media query.** They exist to serve a platform the media query excluded; after a threshold change, check whether the platform is still outside the query. If the platform's window floor (Tauri `minWidth`) now sits inside it, the class block is dead — delete it and its JS class, and add a `doesNotMatch` guard so it stays deleted.
4. **Sweep e2e specs for viewport tables.** Search `e2e/` for `setViewportSize` and viewport→expectation tables; any viewport between the old and new thresholds changes layout mode, and expectations like scroll owner (`main-content` stacked vs `panels-grid` split) flip. Keep a case on each side of the new threshold so both modes stay covered.
5. **Check threshold-conditional state.** Storage keys written per-mode (`map-height` vs `map-split-height`), settings-export prefix lists, and zone-reconciliation listeners all branch on the same predicate — moving the threshold moves which users hit which branch.
## Why This Matters
A threshold defined in one place but encoded in many is the exact shape that produced #6426: two encodings drifted 700px apart and silently hid user panels for an entire viewport band. Encodings that don't contain the number (viewport tables, duplicated class blocks, mode-scoped keys) are invisible to a literal grep, so they surface as CI reds or field bugs after the "complete" change. The constant-plus-alignment-test structure converts future drift from a field bug into a test failure at the PR gate.
## When to Apply
- Any change to a `@media (min-width/max-width)` gate that JS also branches on
- Unifying or splitting per-platform thresholds
- Deleting or adding a platform-specific layout class (`desktop-grid`-style) — first prove the platform's window bounds against the media query it duplicates
## Examples
The e2e encoding that did not contain the number (failed CI on PR #7165 until updated):
```ts
// e2e/dashboard-news-request-budget.spec.ts — the OLD threshold lived in
// this expectation, not in any "1600" literal:
{ label: 'desktop', width: 1280, height: 720, scrollOwner: 'main-content' } // pre-#6417
{ label: 'desktop', width: 1280, height: 720, scrollOwner: 'panels-grid' } // post-#6417
{ label: 'narrow-desktop', width: 850, height: 720, scrollOwner: 'main-content' } // keeps the stacked side covered
```
The dead-block proof (why `.desktop-grid` could be deleted):
```
Tauri window minWidth = 1200 >= SPLIT_LAYOUT_MIN_WIDTH = 900
=> the desktop app always matches @media (min-width: 900px)
=> every rule in .main-content.desktop-grid { ... } is shadowed
```
## Related
- #6417 (feature), PR #7165 (implementation), #6426 (the drift bug this structure closes off)
- [verification-grep-must-cover-every-file-type-it-claims](../conventions/verification-grep-must-cover-every-file-type-it-claims.md) — the same root cause shape: a value duplicated across file types, where the sweep must enumerate every encoding surface
- [deleting-a-parameter-doesnt-delete-the-branch-it-used-to-select](../conventions/deleting-a-parameter-doesnt-delete-the-branch-it-used-to-select.md) — the inverse audit: a change silently killing or activating a branch elsewhere
- [desktop-cls-immediate-tier-panel-mounts-need-deferred-shells](../performance-issues/desktop-cls-immediate-tier-panel-mounts-need-deferred-shells.md) — same dashboard layout surface; watch field CLS after breakpoint changes