mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
fix: the initial wall-model migration resolves every opening conflict itself (#316)
Implements spec revision 4 (green r4). §3.1 — a legacy open_spans/open_to cut never zeroes the atom that carries an existing contour opening: the opening's edges become atom boundaries, the door keeps its real wall and the zero run continues on both sides. §3.2 — an ambiguous carrier resolves deterministically: current host, then distance, thicker cm, smaller id. §3.3 — an opening with no usable carrier persists unhosted: a valid degraded v9 state, inert in the physics, rendered by its own x/y, kept by later writes and re-placeable in the editor (backend schema accepts it). §3.4 — the initial migration never throws over an opening; a post-v9 write that LOST its carrier keeps the fail-closed opening-host refusal. The Python migration mirror implements the same rules with byte-identical output (verified on the span+door fixture including the segment id). The new smoke replays #316 end to end: a conflicted space no longer blocks drawing on an empty plan. The golden scene span-over-door-migrated-dark renders the migrated fixture pinned byte-for-byte to the real writer; two gate mutants revert §3.1 and §3.4 and are red by execution. Issue: #316 User-Visible: yes
This commit is contained in:
@@ -292,13 +292,13 @@ export const CONFIG_FIELD_REGISTRY = Object.freeze([
|
||||
selector: { path: ['spaces', '*', 'openings', '*', 'host', 'kind'], equals: 'wall' },
|
||||
storage: 'House Plan server config',
|
||||
type: "{ kind: 'wall'; id: string; t: number }",
|
||||
default: 'required for contour openings in model v8',
|
||||
default: 'materialized by migration (#316 §3); may persist unhosted after degraded migration',
|
||||
level: 'opening',
|
||||
ui: 'internal opening ownership',
|
||||
runtime: 'binds an opening to a stable contour-wall segment',
|
||||
introduced: 'model v8 / issue #282',
|
||||
status: 'current',
|
||||
migration: 'materialize only when exactly one carrier is proven',
|
||||
migration: 'auto-resolved per #316 §3.1-3.3; unhosted is the degraded terminal state',
|
||||
compatibility: 'legacy unhosted openings remain readable before migration',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2257,6 +2257,35 @@ export const MUTANTS = [
|
||||
replace: " const spacesById = new Map(((this._serverCfg as any)?.spaces || [])",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'span-cut-erases-the-door-wall',
|
||||
// #316 §3.1: легаси-кат обязан щадить атом, несущий проём — иначе дверь
|
||||
// остаётся на нулевой стене и миграция деградирует без нужды.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="keeps its wall through a legacy span cut" '
|
||||
+ 'test/wall-segment-model.test.mjs',
|
||||
because: 'дверь внутри бывшей «границы» стояла в настоящей стене — занулять её атом значит переписать план (#316)',
|
||||
patches: [{
|
||||
file: 'src/wall-segment-model.ts',
|
||||
find: " atom.zeroWall ||= coveredBy(canonicalZeroCuts)\n || (coveredBy(legacyCuts) && !atomCarriesOpening(a, b));",
|
||||
replace: " atom.zeroWall ||= coveredBy(canonicalZeroCuts) || coveredBy(legacyCuts);",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'migration-throws-over-an-opening-again',
|
||||
// #316 §3.4: initial migration никогда не кидает opening-host — реверт
|
||||
// возвращает глобальный блокер рисования beta.3.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="migrates unhosted instead of blocking" '
|
||||
+ 'test/wall-segment-model.test.mjs '
|
||||
+ '&& node demo/smoke_zero_wall_migration_unblocked.mjs',
|
||||
because: 'один конфликтный проём снова заблокировал бы структурные записи во всех пространствах (#316)',
|
||||
patches: [{
|
||||
file: 'src/wall-segment-model.ts',
|
||||
find: " if (initialMigration) { delete opening.host; continue; }",
|
||||
replace: " if (initialMigration) throw new WallSegmentModelError('opening-host', opening.id);",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'preflight-fallback-survives-dialog-close',
|
||||
// CODE-REVIEW-295-r1 M2: инлайн-фолбэк живёт одно показание диалога;
|
||||
|
||||
Reference in New Issue
Block a user