Cierra §10.1, la ultima deuda del eje. `dee2c9e3a` arreglo los dos defectos pero
dejo la FORMA sin guard, y es la que sobrevivio a la migracion §6.3 y a que yo
diera el eje por cerrado en §9.19.
LA FIRMA — dentro de un bloque cuyo prelude tiene `:dir()`, la misma familia
logica (`border`/`padding`/`margin`/`inset`-`inline`) declarada en LAS DOS caras,
EXACTAMENTE UNA con valor neutro (`0`, `auto`, `none`, `initial`, `unset`,
`revert`). Ese desequilibrio ES el espejo cancelado: la propiedad ya se habia
volteado cuando la regla matchea, asi que reubicarla la devuelve al punto de
partida y la deja en el borde opuesto al de sus hermanas.
Es mas estrecha que «bloque `:dir()` con puras logicas» a proposito. Una regla
que CAMBIA un valor bajo RTL sin reubicarlo es legitima —asimetrico por diseño
existe—, y las dos caras con valor son un autor describiendo dos bordes reales.
Lo que delata el defecto es el PAR, una cara apagada. Hay un test negativo por
cada uno de esos casos, que son los que evitan que la regla se vuelva ruido.
LAS TRES DECISIONES que el handoff dejaba abiertas:
- `:dir(ltr)` tambien entra. Igual de sospechosa, y no anade ruido.
- Marcador PROPIO, `rtl-mirror: <reason>`. Reutilizar `rtl-physical:` mentiria:
aqui no hay nada fisico, y un marcador que miente es peor que ninguno. Mismo
mecanismo (`exemptLines` toma ahora el patron por parametro), dos vocabularios.
Un test comprueba que el marcador de RTL-1 NO exime a RTL-2.
- Regla aparte, `lintRtlMirror()` con su propio `RtlMirrorFinding`. Los 14 tests
de RTL-1 quedan intactos y el tipo lleva los campos que importan
(`family`/`neutral`/`payload`) en vez de forzar los de RTL-1.
VERIFICACION, en este orden:
- `rtl-lint.test.ts` 27/27 (14 de RTL-1 intactos + 13 nuevos).
- RECALL con el runner COMPLETO contra el arbol pre-arreglo: restaure los dos
ficheros de `dee2c9e3a^` sobre el arbol, corri `rtl:check` y los devolvi con
`git checkout` en el mismo bloque. **2/2 cazados** (`feed.css:147`,
`tree-view.css:211`). Probar la funcion no basta: el runner es lo que corre.
- `rtl:check` sobre HEAD: 1 error, el de `palabras`, preexistente y excluido.
- `docs:check` 0/0 sobre 564 docs. `check` 77 = linea base.
Un defecto de presentacion salio al hacerlo: el `calc()` multilinea de tree-view
partia el mensaje por el primer salto. Los campos que RTL-2 emite colapsan el
whitespace; con test.
Actualizados los textos que el handoff avisaba que quedarian obsoletos: el
`enforcement:` y §4 del contrato, la fila RTL del build contract, la fila X-1.6
del checklist, y los docblocks de `rtl-lint.ts` y `rtl-check.ts` — que son
doctrina, no adorno.
⚠️ Sigue siendo cierto lo que NINGUNA de las dos reglas ve: leen texto CSS. Un
`transform` inline escrito por JS, un preset de motion compartido y la geometria
SVG siguen necesitando el ojo en RTL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
authority: canonical — the resolution chain, which attribute carries it, and which selector form may read it
authority: canonical — the resolution chain, which attribute carries it, and which selector form may read it
status: current
status: current
enforcement: §4 only — scripts/rtl-check.ts (rule RTL-1, `error`) · `npm run rtl:check`. §1–§3 and §5 have no mechanical guard; they are held by review and by the acceptance rules.
enforcement: §4 only — scripts/rtl-check.ts (rules RTL-1 and RTL-2, `error`) · `npm run rtl:check`. §1–§3 and §5 have no mechanical guard; they are held by review and by the acceptance rules.
related:
related:
resolver: src/uix/soma/direction.ts (activeDir, and why it can return undefined)
resolver: src/uix/soma/direction.ts (activeDir, and why it can return undefined)
@ -271,7 +271,7 @@ The morfo is DNA. If it's incomplete, every downstream layer is incomplete.
| X-1.3 | `eidos-lint-all.ts` invalid count = 0 for this component | error | all | tool:eidos-lint |
| X-1.3 | `eidos-lint-all.ts` invalid count = 0 for this component | error | all | tool:eidos-lint |
| X-1.4 | `npm run check` does not produce errors in this component's files | error | all | tool:check |
| X-1.4 | `npm run check` does not produce errors in this component's files | error | all | tool:check |
| X-1.5 | Component's smoke route loads without console errors (`SMOKE_SCOPE=/uix/components/{kebab} npm run smoke`) | error | all | tool:smoke |
| X-1.5 | Component's smoke route loads without console errors (`SMOKE_SCOPE=/uix/components/{kebab} npm run smoke`) | error | all | tool:smoke |
| X-1.6 | `npm run rtl:check` PASS (RTL-1 — logical inline anchor paired with a physical inline translate). The run walks all of `src/uix/eidos`; there is no per-component scope | error | all | tool:rtl:check |
| X-1.6 | `npm run rtl:check` PASS (RTL-1 — logical inline anchor paired with a physical inline translate; RTL-2 — a `:dir()` rule that turns one logical face off and repaints the other, cancelling a mirror the property had already made). The run walks all of `src/uix/eidos`; there is no per-component scope | error | all | tool:rtl:check |
| **Size (controls)** | the `--size-{k}-*` bundle (height·font·padding·gap·radius·icon) | re-deriving size→font; consuming none of the bundle | size-bundle test (recipe-css-contract) |
| **Size (controls)** | the `--size-{k}-*` bundle (height·font·padding·gap·radius·icon) | re-deriving size→font; consuming none of the bundle | size-bundle test (recipe-css-contract) |
| **Touch hit-area** | §37: `--touch-target` (44px) under `pointer: coarse` only — AREA ≠ VISUAL (`::before` slop for bare markers) | targets <44ontouchwithoutslop;growingthevisual|archetypes.csscoarserules|
| **Touch hit-area** | §37: `--touch-target` (44px) under `pointer: coarse` only — AREA ≠ VISUAL (`::before` slop for bare markers) | targets <44ontouchwithoutslop;growingthevisual|archetypes.csscoarserules|
| **Portal typography** | anchor `font-family`+`line-height`+`color` on the portaled content root | inheriting (falls to serif in the portal) | rule (LIVE) |
| **Portal typography** | anchor `font-family`+`line-height`+`color` on the portaled content root | inheriting (falls to serif in the portal) | rule (LIVE) |
| **RTL** | **logical** properties (`inline/block`, `inset-inline`) for flow; physical `left/right` ONLY as floating *placement* APIs (EID-3 exception) or where the geometry itself is physical (compass handles, polar arcs, a JS-measured offset); branch on direction with **`:dir(rtl)`** — and then the provider MUST stamp the raw `dir`, or `:dir()` only ever sees the inherited direction ([direction contract](../canon/direction-contract.md)) | physical `padding-left`/… in content flow; a logical anchor paired with a physical `translateX` — the anchor flips, the transform does not; `[dir='rtl'] …`, which misses the common no-attribute case and ignores any nearer re-declaration; accepting the prop, running the chain and never stamping — the maths moves, the paint stays behind | RTL-1 · `npm run rtl:check` |
| **RTL** | **logical** properties (`inline/block`, `inset-inline`) for flow; physical `left/right` ONLY as floating *placement* APIs (EID-3 exception) or where the geometry itself is physical (compass handles, polar arcs, a JS-measured offset); branch on direction with **`:dir(rtl)`** — and then the provider MUST stamp the raw `dir`, or `:dir()` only ever sees the inherited direction ([direction contract](../canon/direction-contract.md)) | physical `padding-left`/… in content flow; a logical anchor paired with a physical `translateX` — the anchor flips, the transform does not; `[dir='rtl'] …`, which misses the common no-attribute case and ignores any nearer re-declaration; accepting the prop, running the chain and never stamping — the maths moves, the paint stays behind; a `:dir()` rule that turns one logical face off and repaints the other — the property had ALREADY mirrored, so that cancels it | RTL-1 · RTL-2 · `npm run rtl:check` |
| **RTL · SVG** | a graphic with a READING axis mirrors (invert the scale's pixel range); a RADIAL one does not. `text-anchor` is LOGICAL: leave it alone when the composition mirrors, force the physical one when it does not — see `eidos/components/chart/README.md` §Direction | mirroring *and* flipping the anchor (they cancel); flipping the anchor on a gutter that never moves (the label walks across the graphic); mirroring y values | eye, in RTL — RTL-1 reads CSS text and cannot see SVG attrs or JS-written inline geometry |
| **RTL · SVG** | a graphic with a READING axis mirrors (invert the scale's pixel range); a RADIAL one does not. `text-anchor` is LOGICAL: leave it alone when the composition mirrors, force the physical one when it does not — see `eidos/components/chart/README.md` §Direction | mirroring *and* flipping the anchor (they cancel); flipping the anchor on a gutter that never moves (the label walks across the graphic); mirroring y values | eye, in RTL — RTL-1 reads CSS text and cannot see SVG attrs or JS-written inline geometry |
| **i18n** | `eidos.langs.ts('#?key\|fallback')` + key in the catalog | hardcoded strings / `aria-label`s | rule (LIVE) |
| **i18n** | `eidos.langs.ts('#?key\|fallback')` + key in the catalog | hardcoded strings / `aria-label`s | rule (LIVE) |
| **Color (values)** | role tokens `--color-*` / recipe tokens | raw hex/rgb/hsl/oklch | R-2.1/2.6 · R-4.6 |
| **Color (values)** | role tokens `--color-*` / recipe tokens | raw hex/rgb/hsl/oklch | R-2.1/2.6 · R-4.6 |