Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ad4192e
feat(design): the floor families stop reporting a zero they never cou…
Batuis Aug 20, 2026
23ce3e3
fix(design): timber C24 was entering the concrete pipeline as 24 MPa …
Batuis Aug 21, 2026
234c9b9
style(design): the sheet's control group joins the token system
Batuis Aug 21, 2026
d714368
style(design): the footing mat and the rebar rail leave their private…
Batuis Aug 21, 2026
54ba502
style(design): the viewer's concrete panels join the token system
Batuis Aug 21, 2026
dfa20d8
feat(tokens): the shared status-surface contract
Batuis Aug 21, 2026
695265b
refactor(design): the consumers adopt the status contract
Batuis Aug 21, 2026
2b06d83
style(design): the rest of bucket 1, now that the tokens exist
Batuis Aug 21, 2026
e67e2dc
fix(design): --st-accent stops standing in for a status
Batuis Aug 21, 2026
dfc4bd8
test(design): H1-A — the audit harness, and what it found
Batuis Aug 22, 2026
92c061e
fix(viewer): the 3-D workspace renders in the application's typeface
Batuis Aug 22, 2026
e82753c
docs(design): --st-text-3 measured, and why the obvious fix is wrong
Batuis Aug 22, 2026
4d6b008
fix(design): H1-B — heading order, and the four things H1-A deferred
Batuis Aug 22, 2026
d0ba026
fix(design): the concrete surface's copy leaves --st-text-3
Batuis Aug 22, 2026
3ad9251
docs(design): one proposal for the three shared-chrome defects
Batuis Aug 22, 2026
3f2f409
fix(design): H1-C — the documents stage, on a fixture that reaches it…
Batuis Aug 22, 2026
71dc6a5
fix(store): a refused review no longer costs the user their document
Batuis Aug 22, 2026
acc6aa5
feat(design): the documents stage says what the document IS
Batuis Aug 22, 2026
8b58f9f
docs(i18n): the translation gap, and the two one-line reasons no gate…
Batuis Aug 22, 2026
8b37848
fix(viewer): H1-D — the isolate toggle no longer destroys the focused…
Batuis Aug 22, 2026
bec8afa
test(viewer): the conflict states, on the fixtures that actually prod…
Batuis Aug 22, 2026
1a3c0aa
fix(viewer): back under the 600-line ceiling
Batuis Aug 22, 2026
8a16eb9
docs: the export-record contract, and what each fixture can reach
Batuis Aug 22, 2026
dd664a1
refactor(viewer): the workspace top bar moves to its own file
Batuis Aug 22, 2026
c6abc3f
test(viewer): a hook for the conflict marker, and the defect it found
Batuis Aug 22, 2026
120f15c
fix(section): the lip is measured from the outer face, as both drawin…
Batuis Aug 22, 2026
5d5c143
test(viewer): the absence states, the section cut, the narrow rail an…
Batuis Aug 22, 2026
bfbebb6
docs(section): the evidence for M2 on the outer-face convention
Batuis Aug 22, 2026
97bdc79
test(design): a real refusal, and the state it turns out to be report…
Batuis Aug 22, 2026
33627d7
fix(design): a refused design is reported as REFUSED, not as a reinfo…
Batuis Aug 22, 2026
305debb
docs: export coverage and the C/Z divergence, as an integration problem
Batuis Aug 22, 2026
2b734a3
docs: keep the handoffs true — a stale section, a duplicate, and two …
Batuis Aug 22, 2026
6a7efd7
fix(e2e): three throwaway probes were committed and would run in a QA…
Batuis Aug 22, 2026
c8634d7
docs: the manual QA guide for H1 on 4003
Batuis Aug 22, 2026
6f01d2e
Merge feat/pro-steel-family into feat/pro-concrete-h1
Batuis Aug 26, 2026
82831f6
docs: the two visible changes the base merge brought, for the QA sweep
Batuis Aug 26, 2026
4218984
test(review): four tests still click a button this branch correctly d…
Batuis Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ web/src/lib/wasm

# Playwright artifacts (traces, videos, HTML report). Baselines under
# web/e2e/__screenshots__/ ARE committed.

# Throwaway diagnostics. Three `__probe*.spec.ts` reached `feat/pro-concrete-h1` through a
# `git add -A` and would have run in a QA sweep: no assertions, `console.log` output, and one of
# them with a ten-minute timeout. `__screenshots__` above is the deliberate exception and stays.
web/e2e/__probe*.spec.ts

web/e2e/.artifacts/
web/e2e/.report/
web/test-results/
Expand Down
110 changes: 110 additions & 0 deletions docs/handoffs/cz-divergence-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Divergencia C/Z entre H1 y M1 — problema de INTEGRACIÓN

**Estado: no es un cambio pendiente de H1.** H1 ya aplicó lo que le correspondía en `120f15cc` y
**no volverá a tocar la convención**. Lo que falta está en la rama de M1 y M2 tiene que verificarlo
ahí.

Este documento existe para que la divergencia no se descubra en el momento del merge.

---

## 1. El estado de cada rama, hoy

| | `section-shapes.ts` (C) | `cold-formed.ts` (`partsC` / `partsZ`) |
|---|---|---|
| **`feat/pro-concrete-h1`** | **cara exterior** ✅ `120f15cc` | *el archivo no existe en este árbol* |
| **`feat/pro-steel-m1`** | línea media *(sin tocar)* | **línea media** — pendiente |

Las dos ramas calculan **áreas distintas para la misma designación** hasta que integren. En un
`C 100x50x15x2` son 444 mm² contra 452: `2t²`, un 1,8 % en área y hasta **5,6 % en Iz**.

---

## 2. Por qué H1 hizo sólo la mitad

Es lo que la propia propuesta de M1 pedía:

> *"`section-shapes.ts` no se tocó — contiene también las plantillas de hormigón, así que el cambio
> no sale de M1 de forma unilateral, y tiene que aplicarse a las dos formas a la vez."*

H1 es dueño de ese archivo y lo aplicó. **La otra mitad no la puede aplicar**: en el árbol de H1
`lib/profiles/cold-formed.ts` no existe, y tampoco hay ningún Z — las apariciones de `'Z'` en
`section-drawing.ts` son el comando *closepath* de SVG, que es lo que un grep mío encontró y leyó
mal la primera vez.

Z entró en la rama de M1 en `01da50cb` (geometría C/Z y gramática de designación) y `8f80481e`
(`'Z'` en la unión de formas, y el dibujo del zeta). Ninguno está mergeado acá.

---

## 3. Lo que falta, y es de M1

Dos líneas, tal como las escribió M1 en §4 de `m2-lip-convention-proposal.md`†:

```diff
- { w: t, ht: c, uc: b - t / 2, vc: (h - t) / 2 - c / 2 }, // partsC, labio superior
+ { w: t, ht: c - t, uc: b - t / 2, vc: (h - c - t) / 2 },
```
```diff
- const vLip = (h - t) / 2 - c / 2; // partsZ
+ const vLip = (h - c - t) / 2; // partsZ, y ht: c - t en las dos partes del labio
```

Y una tercera cosa que **no** es de dibujo y es fácil de olvidar:

**`validateColdFormed` / `lipsCollide` tiene que seguir la cota aflojada.** H1 pasó de
`c + tf > h/2` a `c > h/2`. Si el validador conserva la vieja, va a **rechazar secciones que el
cálculo acepta y computa correctamente** — un desacuerdo nuevo, en la dirección opuesta al que se
está cerrando.

---

## 4. Cómo verificarlo en la rama de M1

La evidencia de H1 está en `h1-cz-convention-evidence.md`. Para el espejo, el criterio de
aceptación es el mismo y es reproducible:

1. **Integrar el polígono dibujado y comparar contra el cálculo**, para C **y** para Z. H1 lo hace
en `cold-formed-lip-convention.test.ts` con Green sobre los vértices de `createCShape`; el
mismo método aplica a `createZShape`.
2. **Comprobar en las dos direcciones**: revertir la convención debe hacer fallar el test por
exactamente `2t²` en área. Si no falla, el test no está midiendo el polígono.
3. **`c <= tf` es un canal sin labio**, no un error. `Math.max(0, c - t)` lo cierra por
construcción — el labio útil es ≤ 0 exactamente cuando el dibujo se niega a dibujarlo.
4. **La cota `c > h/2`**, en `lipsCollide` y en el cálculo, con los mismos tres puntos: 0.049 /
0.050 / 0.0501 sobre una sección de 0.100.

---

## 5. Orden de integración, y el riesgo si se invierte

**El espejo tiene que entrar en la misma integración que `120f15cc`, no después.**

- Si **H1 mergea primero sin el espejo**: `section-shapes.ts` calcula por cara exterior y
`cold-formed.ts` por línea media **dentro del mismo árbol**. Dos módulos de secciones que
discrepan es peor que la discrepancia actual entre ramas, porque deja de ser evidente.
- Si **M1 mergea primero sin el espejo**: lo mismo, con los papeles cambiados.
- Si entran juntos: el árbol queda coherente y los tests de las dos ramas se sostienen.

No hay conflicto de merge que avise: son archivos distintos. **Nada va a fallar en el merge**, y
esa es exactamente la razón de este documento.

---

## 6. Un efecto que sobrevive a la integración

Independiente de quién mergee primero, y ya anotado en la evidencia de H1:

`snapshot`/`restore` guarda A e I en vez de rederivarlos desde `built.params`. Así que una sección
`C-custom` **ya guardada** conserva sus números, y una **nueva** obtiene los de cara exterior.

**Un mismo proyecto puede terminar con dos secciones C de la misma designación y distinta área.**
No es defecto de este cambio —se sigue de que las propiedades se persistan— pero es la clase de
cosa que aparece como reporte de usuario meses después, y conviene que esté escrita antes.

---

† `docs/handoffs/m2-lip-convention-proposal.md` **no está en este árbol**: vive en
`feat/pro-steel-m1`, commit `f936f29c`. Se lee con
`git show f936f29c:docs/handoffs/m2-lip-convention-proposal.md`. Lo cito porque es la fuente de la
convención, no porque esté acá.
159 changes: 159 additions & 0 deletions docs/handoffs/h1-cz-convention-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Evidencia para M2 — la convención de cara exterior, aplicada en `120f15cc`

**Rama:** `feat/pro-concrete-h1` · **Commit:** `120f15cc` · **Archivo:** `web/src/lib/data/section-shapes.ts`
**Test:** `web/src/lib/data/__tests__/cold-formed-lip-convention.test.ts` — 9 aserciones

La convención **no se volvió a cambiar**. Este documento es sólo la evidencia pedida.

---

## 1. A, Iy e Iz contra el polígono

El test integra el contorno que **`createCShape` realmente recorre** —Green sobre sus vértices,
vía `shape.getPoints(1)`— y lo compara contra lo que devuelve `computeSectionProperties` para los
mismos parámetros.

| sección | A (mm²) | Iy (mm⁴) | Iz (mm⁴) |
|---|---:|---:|---:|
| `C 100x50x15x2.0` | 444.0 | 718 012.0 | 156 865.0 |
| `C 150x60x20x2.5` | 750.0 | 2 624 843.8 | 378 842.4 |
| `C 200x75x20x3.0` | 1134.0 | 6 993 042.0 | 834 600.2 |
| `C 80x40x12x1.5` | 267.0 | 277 071.2 | 60 707.4 |

**Coinciden con el polígono a 1e-12 absoluto en área y 1e-9 relativo en las dos inercias**, en las
cuatro medidas. Son exactamente los números de la tabla §3.1 de
`m2-lip-convention-proposal.md`†, reproducidos de forma independiente: yo no copié la tabla, el
test integra el contorno.

**Tolerancia relativa y no absoluta**, a propósito: las cuatro secciones abarcan de 267 a 1134 mm²
y de 10⁻⁷ a 10⁻⁵ m⁴, y un solo epsilon no puede ser correcto para las dos puntas.

### Verificado en las dos direcciones

Un test que sólo pasa no prueba que mida algo. Revertí la convención a la línea media y volví a
correr:

```
expected 0.000452 to be close to 0.000444 diff 8.0e-6 (C 100x50x15x2.0)
expected 0.0007625 to be close to 0.00075 diff 1.25e-5 (C 150x60x20x2.5)
expected 0.001152 to be close to 0.001134 diff 1.80e-5 (C 200x75x20x3.0)
expected 0.0002715 to be close to 0.000267 diff 4.5e-6 (C 80x40x12x1.5)
```

Las cuatro diferencias son **exactamente `2t²`** para t = 2, 2.5, 3 y 1.5 mm.

---

## 2. Diferencia antes / después

| sección | A antes | A después | ΔA | `2t²` | ΔIy | ΔIz |
|---|---:|---:|---:|---:|---:|---:|
| `C 100x50x15x2.0` | 452.0 | **444.0** | 8.0 | 8.0 | −1.97 % | −4.99 % |
| `C 150x60x20x2.5` | 762.5 | **750.0** | 12.5 | 12.5 | −1.98 % | −5.01 % |
| `C 200x75x20x3.0` | 1152.0 | **1134.0** | 18.0 | 18.0 | −2.04 % | −5.58 % |
| `C 80x40x12x1.5` | 271.5 | **267.0** | 4.5 | 4.5 | −1.85 % | −4.67 % |

**Iz cae bastante más que Iy** —del 4.7 % al 5.6 % contra ~2 %— y vale señalarlo porque la
propuesta no lo tabulaba: el labio está en la punta del ala, lejos del centroide en z y cerca de
él en y, así que su brazo pesa mucho más en Iz. Para un perfil conformado flexionado en el eje
débil el cambio es del orden del 5 %, no del 2 %.

Ninguna de las cuatro cambia de signo ni de orden de magnitud, y todas **bajan**: la convención de
cara exterior cuenta menos material, nunca más.

---

## 3. `c <= tf` — canal sin labio

El régimen que era peor que un corrimiento de `t/2`. `createCShape` dibuja un canal **sin labio**
cuando `lip <= tf` (línea 140), y el cálculo sumaba `2·c·tl` igual: la app **calculaba una sección
con labio y dibujaba una sin labio**.

`Math.max(0, c - tf)` lo cierra **por construcción**, sin guarda nueva: el labio útil es ≤ 0
exactamente cuando el dibujo se niega a dibujarlo.

Tres aserciones:

1. `c === tf` → A, Iy e Iz coinciden con el contorno dibujado (que es el canal sin labio);
2. `c < tf` → **computa** en vez de ser rechazado, y coincide con el contorno;
3. el resultado **iguala a un `U-custom` de la misma chapa** — la forma más fuerte de decir "sin
labio, ES el canal".

---

## 4. La cota `c > h/2`

Antes: `c + tf > h/2`. Ahora: `c > h/2`. **Es una relajación**, y es la correcta bajo la
convención: los labios se tocan cuando sus profundidades por cara exterior suman `h`.

Aserido en tres puntos sobre una sección de h = 100 mm, tf = 2 mm:

| `c` | antes | ahora |
|---|---|---|
| 49 mm | **rechazada** (49 + 2 > 50) | aceptada |
| 50 mm | rechazada | aceptada (exactamente `h/2`) |
| 50.1 mm | rechazada | rechazada |

**Consecuencia para M2:** `validateColdFormed` / `lipsCollide` tiene que seguir esta cota, o el
validador va a rechazar secciones que `computeSectionProperties` acepta y calcula bien.

---

## 5. Impacto sobre plantillas y modelos guardados

**Plantillas de hormigón: ninguno.** El cambio está contenido en el `case 'C-custom'`.
`section-shapes.ts` también tiene `concrete-square`, `concrete-rect`, `U-custom`, etc., y ninguno
se tocó — verificado por la suite completa: **375 archivos / 7022 tests**, sin un solo fallo, así
que nada dependía de los valores viejos de C.

**Modelos guardados: no se recalculan.** `snapshot`/`restore` guarda A e I, no los reconstruye
desde `built.params`. Así que:

- una sección `C-custom` **ya guardada** conserva sus números y ningún resultado de análisis se
mueve al abrir un `.ded` viejo;
- una sección **nueva**, o cualquier flujo que rederive desde `built.params`, obtiene los valores
de cara exterior.

Eso implica que **un mismo proyecto puede contener dos secciones C con la misma designación y
distinta área** si una se creó antes y otra después. No es un defecto de este cambio —es la
consecuencia de que las propiedades se persistan— pero conviene tenerlo escrito antes de que
aparezca como sorpresa.

---

## 6. Z: **no está en el árbol de H1**

El brief pedía actualizar C y Z juntas. En `feat/pro-concrete-h1` **no hay Z en absoluto**: ni
cálculo en `section-shapes.ts` ni caso de dibujo. Las apariciones de `'Z'` en
`section-drawing.ts` son el comando *closepath* de SVG — lo encontré con un grep y lo leí mal la
primera vez.

El Z vive en `lib/profiles/cold-formed.ts` de M1 (`partsC` / `partsZ`), agregado en `01da50cb` y
`8f80481e`, **sin mergear acá**. Su espejo son dos líneas y está en §4 de la propuesta:

```
vLip = (h - c - t) / 2 y ht: c - t en las dos partes del labio
```

**Estado actual entre ramas:** la C de H1 sigue la cara exterior; la C **y** la Z de M1 siguen la
línea media. Son áreas distintas para la misma designación hasta que integren, y el orden importa:
si se mergea H1 sin el espejo, `cold-formed.ts` y `section-shapes.ts` van a discrepar dentro del
mismo árbol.

---

## 7. Qué revisar

1. Que los números de §1 coincidan con la tabla de la propuesta. **Coinciden**, y en las dos
direcciones.
2. Que la caída de **~5 % en Iz** (§2) sea la esperada. La propuesta no la tabulaba.
3. Que la cota aflojada (§4) sea la deseada, y que `lipsCollide` la siga.
4. **Que el espejo de `cold-formed.ts` entre en la misma integración** (§6). Es lo único que puede
dejar el árbol inconsistente.

---

† `docs/handoffs/m2-lip-convention-proposal.md` **no está en este árbol**: vive en
`feat/pro-steel-m1`, commit `f936f29c`. Se lee con
`git show f936f29c:docs/handoffs/m2-lip-convention-proposal.md`. Lo cito porque es la fuente de la
convención, no porque esté acá.
Loading
Loading