Commit Graph
651 Commits
Author SHA1 Message Date
claude[bot]andSergey Matyunin 7c6e631560 docs: review document for #238
Issue: #238
User-Visible: no
2026-08-22 12:25:05 +03:00
Sergey Matyunin cb19b7d52e feat: measure opening preview to inner faces
Issue: #238
User-Visible: yes
2026-08-22 12:24:49 +03:00
claude[bot]andSergey Matyunin 06c7934667 docs: review document for #238
Issue: #238
User-Visible: no
2026-08-22 12:24:49 +03:00
Sergey Matyunin 5582e9a35d docs(spec): define opening inner-distance guides
Issue: #238
User-Visible: no
2026-08-22 12:24:49 +03:00
Matysh 9e71334cb4 docs: refresh documentation screenshots and require check-docs
Issue: #237
User-Visible: no
2026-08-22 11:42:55 +03:00
claude[bot] 6bd197ffd7 docs: review document for #228
Issue: #228
User-Visible: no
2026-08-22 08:14:31 +00:00
claude[bot]andSergey Matyunin 55d5a560ad docs: review document for #228
Issue: #228
User-Visible: no
2026-08-22 10:56:38 +03:00
Sergey Matyunin 691cea074b fix: close plan repair review findings
Issue: #228
User-Visible: yes
2026-08-22 10:56:38 +03:00
claude[bot]andSergey Matyunin 8fc3ec783b docs: review document for #228
Issue: #228
User-Visible: no
2026-08-22 10:56:23 +03:00
Sergey Matyunin 2f968996b1 fix: make plan drawing fail closed
Issue: #228
User-Visible: yes
2026-08-22 10:56:22 +03:00
claude[bot]andSergey Matyunin 9a131cca25 docs: review document for #228
Issue: #228
User-Visible: no
2026-08-22 10:55:08 +03:00
Sergey Matyunin e44b3c97b0 docs: specify plan drawing repairs
Issue: #228
User-Visible: no
2026-08-22 10:55:08 +03:00
claude[bot] 66ffd6fdda docs: review document for #233
Validate / docs (push) Failing after 23s
Validate / provenance (push) Successful in 1m15s
Validate / process-gate (push) Failing after 1m14s
Validate / changes (push) Successful in 1m0s
Validate / hacs (push) Skipped
Validate / hassfest (push) Skipped
Validate / reuse (push) Successful in 47s
Validate / backend (push) Skipped
Validate / frontend (push) Successful in 8m16s
Validate / golden (push) Failing after 13m4s
Validate / performance_smoke (push) Failing after 13m7s
Validate / smoke (push) Failing after 34m13s
Issue: #233
User-Visible: no
2026-08-22 07:22:40 +00:00
claude[bot] bff47f522b docs: review document for #233
Issue: #233
User-Visible: no
2026-08-22 06:57:57 +00:00
Matysh abfaae3e38 feat: measure resize labels between wall faces
Issue: #233
User-Visible: yes
2026-08-22 09:45:15 +03:00
claude[bot] 969847a4c4 docs: review document for #233
Issue: #233
User-Visible: no
2026-08-22 00:26:11 +00:00
Matysh bc8c368db9 docs(spec): keep a passage full length, read thickness atomically
Spec review r1 returned two blocking findings and both were right.

A measured side that is itself a passage would have been shortened by its
neighbouring walls, while insetContour — the very function the area label
already uses — treats that joint as a flat cap and shortens nothing. Length
and area would have diverged again, at a different boundary, which is the
defect this task exists to remove. The zero rule now comes first and returns
the full centreline length for an open side.

The thickness source was wrong as a matter of fact, not of taste: an
existing test shows thicknessCmAt returns 0 for a whole-edge query against a
partially set thickness, so a split-thickness edge would have silently
stopped shortening. Half-depths now come from roomWallProfile, the atomic
profile that innerContourForRoom already uses for the area, so one edge is
resolved by one mechanism.

Two acceptance criteria and two mutation guards added for the closed
findings.

Issue: #233
User-Visible: no
2026-08-22 03:20:06 +03:00
claude[bot] 0312104527 docs: review document for #233
Issue: #233
User-Visible: no
2026-08-22 00:13:35 +00:00
Matysh 77fa698a27 docs(spec): register the #233 spec in the index
Issue: #233
User-Visible: no
2026-08-22 03:00:05 +03:00
Matysh 7b0747888c docs(spec): measure resize labels between wall faces
Issue: #233
User-Visible: no
2026-08-22 02:58:37 +03:00
claude[bot] 0df5db8b1c docs: review document for #234
Validate / docs (push) Failing after 20s
Validate / reuse (push) Successful in 55s
Validate / changes (push) Successful in 1m13s
Validate / provenance (push) Failing after 1m21s
Validate / process-gate (push) Failing after 1m22s
Validate / hacs (push) Failing after 15s
Validate / hassfest (push) Failing after 11s
Validate / frontend (push) Successful in 3m23s
Validate / backend (push) Failing after 4m25s
Validate / golden (push) Failing after 9m54s
Validate / performance_smoke (push) Failing after 11m9s
Validate / smoke (push) Failing after 20m20s
Issue: #234
User-Visible: no
2026-08-21 16:56:20 +00:00
Sergey Matyunin c8e9597228 fix: resolve chain segment thickness in one place
Issue: #234
User-Visible: yes
2026-08-21 19:42:42 +03:00
claude[bot]andSergey Matyunin 67505f8343 docs: review document for #234
Issue: #234
User-Visible: no
2026-08-21 19:41:37 +03:00
MatyshandSergey Matyunin b2024c6626 docs(spec): state that i18n is untouched, name the zero case
The spec review found the i18n section missing outright — the analysis
comment claimed it was untouched, the document itself said nothing, and a
DoR section cannot be inferred from a comment. It now says so explicitly,
and adds that the absence of i18n files from the diff is part of the
contract rather than an accident.

The waived Low is closed too: the old wallChainSegments treated a recorded
zero as valid while the new contract requires strictly positive values, so
zero is now named in the AC1 examples instead of being derivable from the
prose.

Issue: #234
User-Visible: no
2026-08-21 19:41:37 +03:00
claude[bot]andSergey Matyunin b7a9cc4edf docs: review document for #234
Issue: #234
User-Visible: no
2026-08-21 19:41:37 +03:00
MatyshandSergey Matyunin f4098fcf09 docs(spec): register the #234 spec in the index
Issue: #234
User-Visible: no
2026-08-21 19:41:37 +03:00
MatyshandSergey Matyunin 05b2c74442 docs(spec): define one thickness resolver for a wall chain
Issue: #234
User-Visible: no
2026-08-21 19:41:37 +03:00
claude[bot] 1e2eba95b1 docs: review document for #230
Issue: #230
User-Visible: no
2026-08-21 14:07:33 +00:00
claude[bot] 579d6e5f4e docs: review document for #230
Issue: #230
User-Visible: no
2026-08-21 12:33:54 +00:00
CodexandCodex edf1cca8e3 feat: hatch density is a distance, not a count of units (#230)
Одна и та же стена 15 см выглядела на планах с разным `cell_cm` по-разному:
шаг паттерна был константой в юнитах, а толщина стены переводится в юниты через
`cell_cm`, — значит число полос было пропорционально `1/cell_cm`. Разброс между
крайними масштабами достигал 25 раз, а при `cell_cm ≥ 10` в стену не попадало
и одной полосы: штриховка вырождалась в случайные штрихи или исчезала.

Шаг стал физической величиной: `wallHatchStepUnits(cellCm)` возвращает
`8 × (5 / cell_cm)` — это 9.6 см плана при любом масштабе сетки и ровно
исторические 8 юнитов при эталонном `cell_cm: 5`, так что старые планы не
двигаются. Толщина штриха следует за шагом, поэтому соотношение «штрих к
просвету» тоже перестало зависеть от масштаба.

Формула из описания issue (`cell_cm / 5`) не годилась: она увеличивает шаг там,
где стена и без того тонкая в юнитах, и разброс не исчезает, а растёт — 39
полос против 0.06 на краях диапазона. Множитель обратный; на эту ошибку
поставлен отдельный мутант `hatch-step-inverted`.

Зумовая компенсация `1/zoom` убрана по решению владельца (§4.2 ТЗ): стена,
которая меняет штриховку при зуме, — это тот же дефект, только по другой оси.
От каши на дальнем конце зума защищает второй порог `wallHatchNeedsSolid`
рядом с существующим `wallBodyNeedsSolid`; шаг клампится в [0.5, 80] юнитов,
чтобы патологический `cell_cm` не выродил паттерн.

Статический рендерер (`space-render.ts`) нёс собственную константу 8 и вообще
не знал про зум — то есть уже сегодня расходился с картой при любом зуме,
кроме единицы. Теперь оба читают шаг из одной функции; смок проверяет, что они
согласны между собой, а не только каждый сам с собой.

Два golden-эталона переснято осознанно (`golden:accept -- --reviewed`):
`large-house-zoom-040-dark` (шаг был 20 юнитов, стал 8) и
`large-house-zoom-250-dark` (был 3.2, стал 8). Расхождение осмотрено: меняется
только плотность штриховки тел стен, колонн и перегородок, геометрия и цвета
идентичны. Остальные 80 сцен не тронуты — `accept` переснимает весь набор, и
пять сцен, разошедшихся на шуме рендера, возвращены к прежним байтам вместе с
их хэшами в индексе.

Issue: #230
User-Visible: yes
2026-08-21 15:12:08 +03:00
claude[bot] 115a7d4ccd docs: review document for #230
Issue: #230
User-Visible: no
2026-08-21 11:54:24 +00:00
CodexandCodex 44a35fde0f docs: two golden scenes DO move, and the static renderer needs its own AC (#230 r1)
Issue: #230
User-Visible: no
2026-08-21 14:47:49 +03:00
claude[bot] 6872dc420d docs: review document for #230
Issue: #230
User-Visible: no
2026-08-21 11:46:42 +00:00
Codex 2396044bae docs: spec for #230 — hatch density normalised to plan centimetres
Issue: #230
User-Visible: no
2026-08-21 14:35:29 +03:00
claude[bot] 619516806f docs: review document for #229
Validate / docs (push) Failing after 24s
Validate / changes (push) Successful in 1m10s
Validate / process-gate (push) Failing after 1m14s
Validate / provenance (push) Successful in 1m16s
Validate / reuse (push) Successful in 52s
Validate / hassfest (push) Failing after 33s
Validate / hacs (push) Failing after 47s
Validate / frontend (push) Successful in 7m12s
Validate / backend (push) Failing after 16m13s
Validate / golden (push) Failing after 11m40s
Validate / performance_smoke (push) Failing after 14m46s
Validate / smoke (push) Failing after 32m43s
Issue: #229
User-Visible: no
2026-08-21 10:31:02 +00:00
CodexandCodex fab0c38ca9 refactor: one function owns the junction geometry of a space (#229 r2 M1)
Дефект High-1 был одинаковым в двух местах — и в живом рисовании, и в
«Оптимизировать планы», — потому что каждый вызывающий собирал геометрию
примыканий сам. Ревью r2 справедливо заметило, что и защита получилась
однобокой: юнит и мутант сторожили только оптимизатор, а путь карты — тот, где
дефект и был виден пользователю, — не сторожил никто. Заплатка в виде второго
мутанта-близнеца оставила бы причину на месте: два списка координат, которые
обязаны совпадать, но ничем не связаны.

Поэтому геометрия переехала в `spaceMergeGeometry(space, { excludeDraftId })`:
один источник комнат, колонн и концов черновиков, одни координаты, одно место,
где можно ошибиться. Оба вызывающих теперь строчка вызова.

Покрытие идёт за причиной, а не за симптомом: три юнита в
`test/wall-merge.test.mjs` проверяют масштаб полигонов (включая комнаты в форме
x/y/w/h и комнату без геометрии), исключение активного черновика и сам T-стык к
середине стороны комнаты. Мутанты `partition-merge-rescales-rooms` и
`chain-merge-sees-own-draft` перенацелены на общий модуль и теперь краснеют для
обоих путей сразу: 2 и 1 падение, проверено применением патча.

Сценарий с комнатой в смоке пробовал — не взлетел: рисование в комнату
поднимает `_offerWallFaces`, и цепочка не завершается штатно. Ломать смок под
тест не стал, юниты общего модуля покрывают оба пути честнее.

Issue: #229
User-Visible: no
2026-08-21 13:25:16 +03:00
claude[bot] fe2fcef735 docs: review document for #229
Issue: #229
User-Visible: no
2026-08-21 10:20:16 +00:00
CodexandCodex ed13b4a5ca fix: a room side and a live draft, seen in the right coordinates (#229 r1 H1,H2)
**High-1.** Комнаты хранятся в тех же координатах, что и перегородки:
`roomPoly` отдаёт сырой полигон конфига. Обе обвязки делили его на `NORM_W`
ещё раз, комната уезжала в область ~0.0001, и `junctionAt` не находил ни
одного совпадения. Узел на T-стыке к середине стены комнаты — тот самый
случай, ради которого ТЗ прошло два раунда ревью, — молча исчезал.
Воспроизведено вызовом `optimizePlans`: `partitionsMerged === 1` там, где
ожидается 0.

**High-2.** Завершаемая цепочка к моменту слияния ещё лежит в `room_drafts`:
каждый клик персистит её через `_persistActiveDraftSegment`, а удаляется
черновик строкой ниже вызова слияния. Собственные концы цепочки считались
чужим примыканием, и стык с существующей стеной не срастался. Активный
черновик теперь исключается — ровно так же, как это делает
`plan-snap-overlay` (`activeDraftId`).

Дыры в тестах, которые это пропустили, закрыты по существу, а не заплаткой:

- `demo/smoke_wall_chain_merge.mjs` рисует продолжение реальными кликами через
  `_markupClick`, а не присваиванием `_path`, — то есть исполняет тот путь, на
  котором дефект и жил. Клики задаются в координатах плана и переводятся через
  живой view box, иначе смок целится мимо только что нарисованной стены.
- `test/plan-optimizer.test.mjs` получил комнату с примыканием к середине
  стороны: юниты модуля этого не ловили, потому что передают полигон уже в
  согласованном масштабе, минуя обвязку.
- Мутанты `partition-merge-rescales-rooms` и `chain-merge-sees-own-draft`
  сторожат оба места: проверены применением патча, 1 и 2 падения.

Issue: #229
User-Visible: no
2026-08-21 13:04:20 +03:00
claude[bot] 10d5b5a288 docs: review document for #229
Issue: #229
User-Visible: no
2026-08-21 09:57:36 +00:00
Codex 50a00e41a1 docs: refresh documentation screenshots (#229)
`scripts/check-docs.mjs` держит скриншоты в соответствии с исходниками через
отпечаток `src/**`. Отпечаток был просрочен ещё до этой задачи — проверено
исполнением на чистом dev, — так что перезахват закрывает чужой долг заодно с
изменением, которое отпечаток всё равно бы сдвинуло. Содержательно кадры те
же: меняется только шум рендера.

Issue: #229
User-Visible: no
2026-08-21 12:40:41 +03:00
Codex e6be43b90f feat: a straight wall is one record, not a row of seams (#229)
Рисование прямой стены в несколько кликов оставляло по записи на каждый
отрезок. Швы невидимы, пока их не тронешь: выделение хватает кусок,
перетаскивание ломает стену пополам, толщина задаётся пофрагментно. У стен
комнат этого давно нет — `normalizeWallIntervals` схлопывает каждый сплошной
участок одной толщины. Независимые перегородки жили по другому правилу.

Новый чистый модуль `src/wall-merge.ts` даёт им то же правило:

- `mergeCollinearPartitions` сращивает соседей одинаковой толщины и
  направления до неподвижной точки, но только там, где узел никому не нужен.
  Узел остаётся, если в него приходит третья перегородка, стена комнаты
  (стороной, а не только вершиной), колонна или конец сохранённого черновика.
- Направление выжившей записи канонизируется лексикографически: иначе одна и
  та же физическая стена выходила то a→b, то b→a в зависимости от порядка
  входа, и каждый host.t вдоль неё переворачивался вместе с ней.
- `applyOpeningMoves` переносит проёмы на выжившую запись: и авторитетный
  `host`, и legacy-проекцию `x/y/angle`, которую рисует старый читатель
  конфига (docs/CONFIG-COMPATIBILITY.md, #132). Проекция здесь не кэш —
  канонизация направления разворачивает угол на 180°.

Рисование сращивает только свою цепочку и то, чего она коснулась (§8.6 ТЗ):
молча править чужие швы в стороне оно не вправе — для этого есть
«Оптимизировать планы» с предпросмотром, отчётом и отменой. Оптимизация
проходит по всему пространству без seed-ограничения и отдельной строкой
сообщает, сколько записей исчезло.

Issue: #229
User-Visible: yes
2026-08-21 12:40:34 +03:00
claude[bot] 483c29cc39 docs: review document for #229
Issue: #229
User-Visible: no
2026-08-21 09:01:53 +00:00
Codex 1ecd267138 docs: a room junction is a side, not just a corner (#229 r2 M1)
The tolerance fix in r2 named the room's nearest polygon vertex as the point
of contact, which silently excluded the T-junction — a partition meeting the
middle of a room wall. That is a documented product case (141-wall-junctions
§13.1) and the code already measures distance to the edge, not the vertex
(distToSegment over roomEdges). Merging would have run straight through a
legitimate node.

AC2 now proves the room case with a T-junction into the middle of a long
side, and a mutant restores the vertex-only search.

Issue: #229
User-Visible: no
2026-08-21 11:55:51 +03:00
claude[bot] 5c560df1a2 docs: review document for #229
Issue: #229
User-Visible: no
2026-08-21 08:54:11 +00:00
Codex ecc3d6a88b docs: close M1, M2 and M3 from the spec review of #229
M1: "merge across the whole space when a chain ends" quietly overreached the
owner's split — drawing fixes its own seam, the optimiser fixes what has piled
up, and only the latter comes with a report and an undo. Section 8.6 now scopes
it to the connected component the new chain belongs to.

M2: the tolerance for "something else meets here" was only defined for a
partition-to-partition joint. Room edges, columns and drafts now use the same
EPS_JOIN — a gap cannot be a junction in one case and not in another.

M3: an opening also carries a materialised x/y/angle projection that
CONFIG-COMPATIBILITY (#132) requires to stay in step with its host, and the
code already re-materialises it after every host change. Merging is such a
change; AC3 now fails if only host.t is recomputed and the projection goes
stale.

Issue: #229
User-Visible: no
2026-08-21 11:46:36 +03:00
claude[bot] 5b8b6899da docs: review document for #229
Issue: #229
User-Visible: no
2026-08-21 08:43:01 +00:00
Codex 81210f7afc docs: spec for merging collinear wall segments
Written on the owner's decisions of 2026-08-21: merge as the chain is
finished, sweep already-drawn plans from "Optimise plans", and keep merging
even when an opening sits on the seam.

The part that is easy to miss is that last one. An opening stores its
position as a fraction of its host's length, so merging two partitions
changes the length under it and moves the door unless the fraction is
recomputed. AC3 therefore checks the door's coordinates in plan units, not
that a field was rewritten.

Issue: #229
User-Visible: no
2026-08-21 11:32:07 +03:00
claude[bot] 90a00670a7 docs: review document for #220
Issue: #220
User-Visible: no
2026-08-20 21:41:11 +00:00
Codex 8369c0e839 feat: reorder space tabs by dragging one to a new position
The order of config.spaces used to be whatever order the spaces were created
in, and there was no way back other than deleting a space and drawing it
again.

The gesture is deliberately narrow — mouse, editors only. The same tabs are
the primary way to switch spaces in View, where touch is first class, so a
drag there would compete with the tap that switches. Recorded in the spec as
"Touch editor: not exposed".

The part that needed care is not the drag. Position in the array feeds three
things: the marker placement fallback, the swipe neighbour and a positional
`floor`. So the write that stores the new order also writes down the
placement that used to depend on it: a marker with neither an explicit space
nor an area that names one gets the space it has right now. Both changes go in
one save; splitting them would leave a window in which markers move on their
own. The positional `floor` cannot be fixed from here, so the card says so
once.

Issue: #220
User-Visible: yes
2026-08-21 00:27:35 +03:00
claude[bot] 30bf18f508 docs: review document for #220
Issue: #220
User-Visible: no
2026-08-20 20:37:09 +00:00