mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
File diff suppressed because one or more lines are too long
@@ -675,6 +675,43 @@ export async function prepareGoldenScenario(page, scenario) {
|
||||
await card.updateComplete;
|
||||
await settleMode(card);
|
||||
}
|
||||
// #304: the two existing hidden-wall golden scenes also guard the static
|
||||
// architectural overlay in every Plan tool. Finish in draw so the reviewed
|
||||
// pixels stay stable; a missing/changed tool projection fails before capture.
|
||||
if (scenario.hiddenWallDiagnostics) {
|
||||
const tools = [
|
||||
'select', 'draw', 'column', 'merge', 'split', 'resize',
|
||||
'opening', 'boundary', 'wallthick', 'delroom',
|
||||
];
|
||||
const snapshot = () => {
|
||||
const overlay = card.renderRoot.querySelector('[data-hp="plan-snap-overlay"]');
|
||||
if (!overlay || overlay.getAttribute('pointer-events') !== 'none') return null;
|
||||
return JSON.stringify({
|
||||
lines: [...overlay.querySelectorAll('.plan-snap-line')].map((line) => [
|
||||
line.getAttribute('data-key'), line.getAttribute('x1'), line.getAttribute('y1'),
|
||||
line.getAttribute('x2'), line.getAttribute('y2'),
|
||||
]),
|
||||
endpoints: [...overlay.querySelectorAll('.plan-snap-node[data-kind="endpoint"]')]
|
||||
.map((node) => [node.getAttribute('data-key'), node.getAttribute('cx'), node.getAttribute('cy')]),
|
||||
});
|
||||
};
|
||||
let expected = null;
|
||||
for (const tool of tools) {
|
||||
card._tool = tool;
|
||||
card._clearPlanSnapHover();
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
const current = snapshot();
|
||||
if (expected == null) expected = current;
|
||||
if (!current || current !== expected) {
|
||||
throw new Error(`golden plan-axis parity failed for ${tool}: ${scenario.id}`);
|
||||
}
|
||||
}
|
||||
card._tool = 'draw';
|
||||
card.requestUpdate();
|
||||
await card.updateComplete;
|
||||
await frame();
|
||||
}
|
||||
if (scenario.wallUnionIsolation) {
|
||||
const result = card._wallUnionGeometry?.();
|
||||
const paths = [...card.renderRoot.querySelectorAll('[data-hp="wall"]')];
|
||||
|
||||
@@ -236,7 +236,7 @@ const out = await page.evaluate(async () => {
|
||||
card._activateMarkupTool('select');
|
||||
await update();
|
||||
const diagnostic = root().querySelector('[data-hp="hidden-wall-diagnostic"]');
|
||||
result.otherPlanToolsHaveHiddenDiagnostic = !overlay() && !!diagnostic
|
||||
result.otherPlanToolsKeepFullOverlayAndHiddenDiagnostic = !!overlay() && !!diagnostic
|
||||
&& diagnostic.querySelectorAll('.hidden-wall-line').length === 1
|
||||
&& diagnostic.querySelectorAll('.hidden-wall-node').length === 2;
|
||||
const editorVirtualWalls = root().querySelector('.openwalls');
|
||||
@@ -256,9 +256,96 @@ const out = await page.evaluate(async () => {
|
||||
result.hiddenDiagnosticPaintsBeforeTransientSnap = !!(
|
||||
drawDiagnostic.compareDocumentPosition(drawSnap) & Node.DOCUMENT_POSITION_FOLLOWING
|
||||
);
|
||||
|
||||
// #304 regression fixture: one room plus two independent walls which overlap
|
||||
// its left/bottom axes and continue beyond them. The six source endpoints are
|
||||
// the exact topology from the field report, reduced to deterministic data.
|
||||
const parityCfg = {
|
||||
model_version: 7,
|
||||
spaces: [{
|
||||
id: 'axis-parity', title: 'Axis parity', cell_cm: 5, view_box: [0, 0, 1, 0.75],
|
||||
rooms: [{
|
||||
id: 'room', name: 'Room', area: null,
|
||||
poly: [[0.2, 0.2], [0.6, 0.2], [0.6, 0.6], [0.2, 0.6]],
|
||||
}],
|
||||
room_drafts: [], openings: [], open_spans: [],
|
||||
partitions: [
|
||||
{ id: 'vertical', a: [0.2, 0.05], b: [0.2, 0.6], cm: 20 },
|
||||
{ id: 'horizontal', a: [0.2, 0.6], b: [0.9, 0.6], cm: 20 },
|
||||
],
|
||||
settings: { fill_mode: 'none', show_borders: true, show_names: false },
|
||||
}],
|
||||
markers: [], settings: {},
|
||||
};
|
||||
card._serverCfg = JSON.parse(JSON.stringify(parityCfg));
|
||||
card._layout = {};
|
||||
card._space = 'axis-parity';
|
||||
card._modelCache = null;
|
||||
card._frame = null;
|
||||
card._activeDraftId = null;
|
||||
card._path = [];
|
||||
card._cfgEpoch++;
|
||||
card._setMode('plan');
|
||||
await update();
|
||||
|
||||
const staticSnapshot = () => {
|
||||
const current = overlay();
|
||||
return JSON.stringify({
|
||||
lines: [...current.querySelectorAll('.plan-snap-line')].map((line) => [
|
||||
line.getAttribute('data-key'), line.getAttribute('data-source-kind'),
|
||||
line.getAttribute('x1'), line.getAttribute('y1'),
|
||||
line.getAttribute('x2'), line.getAttribute('y2'),
|
||||
]),
|
||||
endpoints: [...current.querySelectorAll('.plan-snap-node[data-kind="endpoint"]')]
|
||||
.map((node) => [
|
||||
node.getAttribute('data-key'), node.getAttribute('cx'), node.getAttribute('cy'),
|
||||
]),
|
||||
});
|
||||
};
|
||||
const tools = [
|
||||
'select', 'draw', 'column', 'merge', 'split', 'resize',
|
||||
'opening', 'boundary', 'wallthick', 'delroom',
|
||||
];
|
||||
let expectedSnapshot = null;
|
||||
let everyToolMatches = true;
|
||||
let everyToolIsPassive = true;
|
||||
let everyToolPaintsAboveWalls = true;
|
||||
let thicknessHasSixNodes = false;
|
||||
for (const tool of tools) {
|
||||
card._tool = tool;
|
||||
card._clearPlanSnapHover();
|
||||
await update();
|
||||
const current = overlay();
|
||||
const bodies = root().querySelector('.wallbodies');
|
||||
const snapshot = current ? staticSnapshot() : null;
|
||||
if (expectedSnapshot == null) expectedSnapshot = snapshot;
|
||||
everyToolMatches &&= !!current && snapshot === expectedSnapshot;
|
||||
everyToolIsPassive &&= current?.getAttribute('pointer-events') === 'none'
|
||||
&& getComputedStyle(current).pointerEvents === 'none'
|
||||
&& !current.querySelector('.plan-snap-node[data-active="true"]');
|
||||
everyToolPaintsAboveWalls &&= !!bodies && !!current
|
||||
&& !!(bodies.compareDocumentPosition(current) & Node.DOCUMENT_POSITION_FOLLOWING);
|
||||
if (tool === 'wallthick') {
|
||||
thicknessHasSixNodes = current.querySelectorAll(
|
||||
'.plan-snap-node[data-kind="endpoint"]',
|
||||
).length === 6 && current.querySelectorAll('.plan-snap-line').length === 6;
|
||||
}
|
||||
}
|
||||
result.allPlanToolsShareStaticAxesAndNodes = everyToolMatches;
|
||||
result.allPlanToolOverlaysStayPointerTransparent = everyToolIsPassive;
|
||||
result.allPlanToolOverlaysPaintAboveWallBodies = everyToolPaintsAboveWalls;
|
||||
result.thicknessShowsCompleteSixNodeFixture = thicknessHasSixNodes;
|
||||
|
||||
card._setMode('devices');
|
||||
await update();
|
||||
const devicesHaveNoPlanOverlay = !overlay();
|
||||
card._setMode('decor');
|
||||
await update();
|
||||
const decorHasNoPlanOverlay = !overlay();
|
||||
card._setMode('view');
|
||||
await update();
|
||||
result.viewHasNoOverlay = !overlay()
|
||||
result.nonPlanModesHaveNoOverlay = devicesHaveNoPlanOverlay && decorHasNoPlanOverlay
|
||||
&& !overlay()
|
||||
&& !root().querySelector('[data-hp="hidden-wall-diagnostic"]');
|
||||
|
||||
return result;
|
||||
|
||||
Vendored
+4
-3
File diff suppressed because one or more lines are too long
@@ -2,6 +2,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Every Plan editor tool now shows the same complete set of wall axes and
|
||||
structural endpoints. Switching from Walls to Thickness no longer hides room
|
||||
axes, while each tool keeps its own hints and live preview
|
||||
([#304](https://github.com/Matysh/houseplan-card/issues/304)).
|
||||
- Re-importing an already imported space no longer grows nested internal-ID
|
||||
prefixes. Preview and Apply now share one immutable candidate, safely repair
|
||||
only uniquely proven references to earlier copies, and preserve ambiguous
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Во всех инструментах редактора Плана теперь отображается один и тот же полный
|
||||
набор осевых линий и узлов стен. При переходе из «Стены» в «Толщина» оси
|
||||
комнат больше не исчезают, а подсказки и предпросмотр активного инструмента
|
||||
остаются независимыми ([#304](https://github.com/Matysh/houseplan-card/issues/304)).
|
||||
- Повторный импорт уже импортированного пространства больше не наращивает
|
||||
служебные префиксы внутренних ID. Предпросмотр и применение теперь используют
|
||||
один и тот же неизменяемый кандидат, безопасно восстанавливают единственные
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
||||
| Item | State |
|
||||
|---|---|
|
||||
| Version | **v1.67.0-rc.2 candidate** everywhere (manifest, const.py, package.json, CARD_VERSION) |
|
||||
| Current local cycle | v1.67.0-rc.1 is published. Current `dev` makes `Esc` detach an accepted Walls chain without deleting it (#294), removes only proven-redundant hidden wall geometry during Optimize (#296), preserves neighbouring thickness records through Resize (#298), keeps wall-thickness records split at shared/outer role boundaries (#299), and limits Resize labels, highlights and room-area readouts to the geometry affected by the gesture (#300). v1.67.0-rc.2 packages these S8 changes and remains gated by the full exact-SHA Validate before publication. |
|
||||
| Current local cycle | v1.67.0-rc.1 is published. Current `dev` makes `Esc` detach an accepted Walls chain without deleting it (#294), removes only proven-redundant hidden wall geometry during Optimize (#296), preserves neighbouring thickness records through Resize (#298), keeps wall-thickness records split at shared/outer role boundaries (#299), and limits Resize labels, highlights and room-area readouts to the geometry affected by the gesture (#300). v1.67.0-rc.2 packages these S8 changes. The next candidate also keeps the complete wall-axis/endpoint overlay identical across every Plan tool (#304) after normal issue review. |
|
||||
| Hidden Labs Stage | #89 Stage 1 ships in v1.63.0-beta.1. #122 Stage 2 ships in v1.64.0 and evolves the same hidden, expiring `iso` experiment with matte walls, a low exterior floor edge, restrained shared shadows and live vertical door/window/gate panels. Flat remains default; editors and `houseplan-space-card` remain flat; live floor effects and HA actions remain unchanged. Public activation remains a separate task. |
|
||||
| Workflow | Superseded 2026-08-12: the pre-1.62 rule of "local edits without tests or commits" is **dead** — since release 1.62 every product change follows `PROCESS.md` (issue in `S5-ready`+, branch `issue/<NN>-slug`, trailers on every commit, review pipeline; `AGENTS.md` is the summary). Release mechanics below remain current. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first): only significant user changes get individual bullets, while minor/code-only work is grouped as `Мелкие исправления и улучшения` / `Small fixes and improvements`; every body ends with separate links to the Russian and English changelogs. Detailed RU/EN changelog bullets may link the corresponding closed GitHub Issues; open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- <tag> --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand |
|
||||
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records; their labels carry priority and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
|
||||
|
||||
+7
-5
@@ -716,11 +716,13 @@ when every one of its segments is fully redundant; a free, partly covered or
|
||||
thicker chain remains byte-for-byte unchanged. The report counts absorbed
|
||||
independent-wall sections and removed whole chains, not source records.
|
||||
|
||||
In the Plan editor, an independent wall or saved chain hidden under other wall
|
||||
bodies retains a thin centre axis and its original endpoint nodes above the
|
||||
masonry. These pointer-transparent diagnostics do not change snapping or
|
||||
selection and are absent from View. They disappear after Apply only when the
|
||||
corresponding independent geometry was safely absorbed or removed.
|
||||
Every Plan editor tool draws room, independent-wall and saved-chain centre axes
|
||||
and endpoint nodes through the same layer above wall bodies. An independent
|
||||
wall or saved chain hidden under other masonry additionally retains its source
|
||||
diagnostic axis and nodes. These pointer-transparent layers do not change
|
||||
snapping or selection and are absent outside the Plan editor. The diagnostic
|
||||
disappears after Apply only when the corresponding independent geometry was
|
||||
safely absorbed or removed.
|
||||
|
||||
Old positions are classified before Apply. A position whose room label, device
|
||||
or light-group owner is proven absent is removed automatically and counted by a
|
||||
|
||||
@@ -1500,10 +1500,12 @@ show_signal: true
|
||||
|
||||
Отчёт считает поглощённые максимальные участки независимых стен, а не число
|
||||
исходных записей, и отдельно — только целиком удалённые сохранённые цепочки.
|
||||
В редакторе Плана ось и исходные узлы независимой стены или цепочки, скрытой под
|
||||
другой кладкой, рисуются поверх всех стен. Этот диагностический слой не
|
||||
перехватывает указатель, не меняет привязку и отсутствует в режиме просмотра;
|
||||
после Apply он исчезает только вместе с доказанно избыточной геометрией.
|
||||
Во всех инструментах редактора Плана оси и конечные узлы комнат, независимых
|
||||
стен и сохранённых цепочек рисуются одним и тем же слоем поверх тел стен. Для
|
||||
независимой стены или цепочки, скрытой под другой кладкой, дополнительно
|
||||
сохраняется её исходная диагностическая ось и узлы. Эти слои не перехватывают
|
||||
указатель, не меняют привязку и отсутствуют вне редактора Плана; после Apply
|
||||
диагностика исчезает только вместе с доказанно избыточной геометрией.
|
||||
|
||||
<!-- docs-section: multiple-cards -->
|
||||
|
||||
|
||||
@@ -17575,7 +17575,10 @@ class HouseplanCard extends LitElement {
|
||||
opacity="${modeVisual?.editorWeight ?? 1}">${this._renderOpeningPlacementPreview()}</g>` : nothing}
|
||||
${opMeasure ? this._renderOpeningDimensionGuides(opMeasure) : nothing}
|
||||
${opMeasure?.guide ? this._renderOpeningCenterTick(opMeasure.guide) : nothing}
|
||||
${this._markup && this._tool === 'draw' ? svg`<g class="hp-editor-only-layer"
|
||||
${''/* Static architectural axes and endpoints are a Plan-editor
|
||||
invariant. Tool-specific hover/snap state is resolved inside
|
||||
the overlay and remains exclusive to Walls. */}
|
||||
${this._markup ? svg`<g class="hp-editor-only-layer"
|
||||
opacity="${modeVisual?.editorWeight ?? 1}">${this._renderPlanSnapOverlay()}</g>` : nothing}
|
||||
${disp.hideOpenings && !this._markup
|
||||
? nothing
|
||||
@@ -19838,7 +19841,7 @@ class HouseplanCard extends LitElement {
|
||||
}
|
||||
|
||||
private _renderPlanSnapOverlay(): TemplateResult {
|
||||
if (!this._markup || this._tool !== 'draw') {
|
||||
if (!this._markup) {
|
||||
return svg`` as unknown as TemplateResult;
|
||||
}
|
||||
const geometry = this._planSnapGeometrySnapshot().value;
|
||||
|
||||
@@ -33,6 +33,31 @@ test('collector includes room rectangles, polygons, saved drafts and partitions'
|
||||
assert.ok(geometry.endpoints.some((entry) => entry.point[0] === 150 && entry.point[1] === 100));
|
||||
});
|
||||
|
||||
test('room with two coincident extended partitions keeps six structural endpoints', () => {
|
||||
const geometry = buildPlanSnapGeometry({
|
||||
space: space({
|
||||
rooms: [{
|
||||
id: 'room', area: null,
|
||||
poly: [[200, 200], [600, 200], [600, 600], [200, 600]],
|
||||
}],
|
||||
partitions: [
|
||||
{ id: 'vertical', a: [200, 50], b: [200, 600], cm: 20 },
|
||||
{ id: 'horizontal', a: [200, 600], b: [900, 600], cm: 20 },
|
||||
],
|
||||
}),
|
||||
});
|
||||
assert.equal(geometry.segments.length, 6);
|
||||
assert.deepEqual(geometry.endpoints.map((entry) => entry.point)
|
||||
.sort((left, right) => left[0] - right[0] || left[1] - right[1]), [
|
||||
[200, 50],
|
||||
[200, 200],
|
||||
[200, 600],
|
||||
[600, 200],
|
||||
[600, 600],
|
||||
[900, 600],
|
||||
]);
|
||||
});
|
||||
|
||||
test('coincident endpoints and axes are deduplicated independently of direction', () => {
|
||||
const geometry = buildPlanSnapGeometry({
|
||||
space: space({
|
||||
|
||||
Reference in New Issue
Block a user