mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
f10c5b437472ac8573e1db48561ecf35163ca967
669
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d8e09cd1a0 |
test(harness): close three smoke-select and fixture blind spots (#754)
Three independent blind spots in the test harness. 1. smoke-select read symbols only from changed lines of a --unified=0 diff. An edit to the arguments of a multi-line call names nothing: #741 ( |
||
|
|
a7b02db609 |
fix(process): import and type-only lines no longer raise risk (#755)
The risk table of #707 judged every non-comment line of a class A file as code. On the history since 15.09 that raised #741 from ship to show for a removed interface member that never reaches JS, and put false classes on #624 (removed imports), #693/#694 (stairs-view is rendering, not geometry) and #725 (the config fingerprint memo is not the config schema). - Lines of module syntax and TypeScript types give no risk, like comments: `import …`, `export … from …`, `export type …`, the head of `interface X` or `type X =`, and the lines inside such a block (indented, plus the closing line). The block state per side of a change block starts from the hunk context git writes after `@@ … @@` and follows every unindented line of the block, so a member under `@@ … @@ export interface X {` and a whole interface added in one hunk are judged alike. Only `.ts`, and not in the `migration` area: there the types are the config contract (#588, #649). - `stairs*` is narrowed to the stairs model (`stairs`, `stairs-box`, `stairs-editor-model`); `config-*` to writing and adopting the config (`config-adoption`, `config-store`, `config-reload-authority`, `config-write-conflict`). - A replaced line is one piece of evidence: a removed line whose counterpart in the same change block hits the same class is folded into it instead of printing `path:N (удалена)` next to `path:N`. classifyRisk stays a pure function over the diff text. On the history the raising classes change for #741 (none), #693 (visual only), #694 (no geometry), #725 (perf only) and #624 (no devices/perf); migration on #588, #612, #649 and #661 stays. PROCESS.md §5 names the new exemption. Issue: #755 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
f5a6b47b39 |
feat(process): the pipeline records model usage in the review document's machine block (#737)
The weekly process metrics weigh tracks and the nightly ship review in the order quality, speed, tokens (#707), but the third axis had no source: the claude-code-action step hides usage from the Actions log on purpose, nothing read its execution_file, and the #728 reader printed "no data" every week. scripts/model-usage.mjs is the single module that builds and parses the line: `<!-- hp:usage input_tokens=N output_tokens=N cache_creation_input_tokens=N cache_read_input_tokens=N num_turns=N -->` (sums over every model in the last `result` message, `result.usage` when modelUsage is absent) or `<!-- hp:usage-none reason=<code> -->`. Only the result message is read; the rest of the file holds tool results, and no byte of it is printed. A new step right after Review in both model_review jobs (always(), continue-on-error) hands the line out as the job output `usage`. Usage is a reporting figure like the stage duration, so it travels as a job output and not through the sealed artifact: REQUIRED_FILES and the #556 gate are unchanged. Publication treats the line as untrusted input and writes the normalized form as the last line of the anchor block (review-doc-guard --anchor --usage=) or right after the SHIP-REVIEW block; empty becomes reason=missing, anything off-format reason=invalid. The #728 reader now takes the line only from the machine block: a reviewer quoting the previous round in prose no longer doubles its usage, and "no data" is counted as missing, never as zero. PROCESS.md §10.4 documents the source, the format and why it is a job output. Issue: #737 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
4091d83af3 |
fix(editor): a render no longer restarts a failed runtime load (#757)
The lazy-runtime contract (#353) says a non-terminal failure waits for the next explicit intent and that there are no background retries. But _renderBody calls ensure() on every repaint while a surface waits for the editor or onboarding runtime, and to the loader that call was indistinguishable from an intent. Surfaces the core opens without the runtime - the kiosk size dialog after a 3 s hold, the floor import wizard on an empty plan, a dialog a warm remount revives - therefore turned one failure into a loop: the loader's own state change, the toast and its expiry, every hass tick repainted, started a new cycle and showed a new toast every ~3.5 s. A wall tablet whose old hashed chunks answer 404 after an integration update sat in that loop forever. EditorRuntimeLoader.ensure takes an intent: the render calls it as 'reconcile'. A reconcile starts the first cycle a surface needs, but after a non-terminal failure it returns false without loading until an explicit ensure() - a tab, an opener, _requestMode, "Add space" - has started a new cycle. Explicit calls, the terminal fingerprint failure, ready and an in-flight cycle behave as before, for every loader instance. The card's render lines stay line-neutral. smoke_lazy_editor_chunk gains the three surfaces offline through their real paths (a 3 s touch hold on a kiosk card, an empty plan pushed by the server, General settings revived by a remount): one cycle, one notice and an idle loader over 8 s, then the Plan tab and "Add space" heal. On dev: 4 requests / 3 notices, 6 / 2 and 3 / 2. The loader unit test pins reconcile versus intent; the mutant render-reconcile-restarts-editor-runtime-cycle is guarded by the smoke. Issue: #757 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
a0f72280ee |
fix(plan): key room shapes by space and id so a floor switch never repaints a stranger (#742)
The flat room list was a bare map(), so Lit reused room nodes by position.
On a floor switch the previous floor's room node became the new floor's room
and `.room { transition: 0.12s }` drew its fill and stroke in from the old
computed values: one paper-white frame, then two or three frames darker than
the final fill (alpha rises while fill-opacity falls), then the fill. Between
two filled floors the fill bled in from the other floor's colour.
The list is now keyed(space.id, repeat(rooms, (r, i) => r.id || i, ...)), the
shape #534 settled on for openings and markers. The outer key handles the
floor switch, including room ids repeated on two floors (ids are unique only
within a space); the inner key keeps a node bound to its room inside a space,
where inserts, merges, splits and the editor filter shift positions. An
id-less room keys by its numeric index, which never equals a string id. The
transition itself stays: it smooths hover and a real fill change on the same
floor.
Witness: a new section of smoke_space_switch_transitions, before physicalize
(after it the first room changes template branch and the node is recreated
anyway). Red on dev: five transitions on g1, node r1 reused for g1, computed
fill rgba(0, 0, 0, 0) / 1; room nodes swapped by an insert; same-id case
reuses r1. A real custom_fill change still runs a fill transition (catches
`transition: none`). Two mutants: inner key removed, outer key removed.
Issue: #742
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
|
||
|
|
9324d81b5f |
perf(iso): keep the theme paper across floor switches in 2.5D (#739)
In 2.5D with a backdrop image every floor switch rendered the card twice before the first frame. The paper key of the first-frame state (#654) held the space id, so each switch cleared the ready paper: the first render inserted the loading veil, updated() probed the computed card background with a temporary span and asked for a second full update, which removed the veil again. The colour itself never changed: it is the theme card background, and no space sets those variables. Locally this second pass was about 50 ms per warm switch on the large house. The paper under a backdrop is now resolved once per theme identity (dark mode, default and dark default theme, theme) and card mode. The state keeps the resolved paper of the current theme and mode beside the current paper, so a floor with a backdrop is ready in prepare() when that paper is known -- also after a drawn floor in between -- and the switch renders once: no veil, no probe, no second update. A drawn plan keeps its white paper without the DOM. Any change of the theme identity or the mode, also one made in Flat or in an editor, drops the kept paper, so the first backdrop floor after load, a theme change and a trip to an editor take the #654 path unchanged. isoPaperContext still takes the floor; it deliberately leaves it out of the identity. Witnesses: the #739 unit test is red on dev at "a floor switch shows no veil" and on a key-only variant (space dropped, no theme cache) at "drawn -> backdrop keeps the known theme paper"; the new smoke_iso_floor_switch is red on dev (2 updates, 1 colour probe and a veil insertion in every click task). The iso-paper-resolved-per-floor mutant puts the floor back into the theme identity. Issue: #739 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
0e44f8a69f |
feat(process): a track:ask draft may be written during the spec review (#729)
On track:ask every spec review round is 10-45 minutes of waiting, and rule #1 kept the author idle for all of it. Rule 10 (#738) judges a class A commit by its author date, so code written in the S4-spec-review epoch was always refused: nothing told a draft written against the reviewed text from a violation. The owner allowed changing rule #1 for this (decision 2026-10-01). A draft commit carries `Spec-Draft: sha256:<issueBodyDigest(body)>`, the hash the pipeline already writes as "Тело issue" into the review document anchor. Rule 10 accepts a class A commit written in S4 only when its S4 epoch (a repeated S4 does not restart it) was closed by S5-ready, the track at the author date was ask, and the trailer equals the body of the green, High 0 SPEC-REVIEW added inside that epoch, read from the range head or origin/dev. The first failing check is the one finding: trailer format, track, how the epoch ended, the missing document with a `git fetch origin dev` hint, or both hashes and the document name. A trailer on a commit written in an allowed epoch is a warn. Without the document reader rule 10 is exactly #738; main always passes one, and it reads git only when the range holds a draft. The task packet tells S4 on ask that a local draft is allowed while the branch stays closed, prints the trailer line, and in S5/S6 names the green spec review, whether the body changed since, and the --report check before push. SPEC-REVIEW documents are a separate input (specDocs) from the branch and origin/dev, so the previous verdict and the AC witness keep their source. PROCESS.md gets §11.8 and the points that refer to it (§1, §2.4-2.6, §3 item 1, §7.2, §9, §10.2 item 10, §12); AUTHOR.md, REVIEWER.md and AGENTS.md follow, with three new key rules in process-digests. The pre-push hook fixture copies the modules process-gate now imports. Issue: #729 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
36ebabaaec |
fix(moon): the status line after a warm revive of General settings (#731)
#718 K7 takes the moon status once per opening of General settings, outside the draft. A warm remount revives the open dialog on a new card instance, but `_warmReviveDialog` restored only the draft: the new instance had no opening of its own, so the "Now: ..." line never came back. A revive is an opening too. The `settings` branch now asks for the status the way `_openSettingsDialog` does - through the lazy editor runtime (`_openMoonStatus` -> `openMoonStatus`): at once when the runtime is there (an editor revives after `_requestMode(..., adopt)` has installed it), after it loads in View; once per revive and only while that revived dialog is still open. The snapshot of now, `hass.config` and `sun.sun` is the revive's own, nothing of the dead instance's opening is carried over, the draft key and the dirty flag do not change. The View graph gets no static moon-status import; other dialog kinds never ask for the moon chunk. demo/smoke_moon_status.mjs gains the revive scenarios - View, the plan editor, a revive while the chunk is still loading, a space-dialog revive that must not load the chunk; the first three are red on dev. test/moon-settings.test.mjs executes the revive as a new opening; the wiring itself is proven by the smoke, not by reading the monolith as text (#624). docs/SUN.md and docs/WARM-REMOUNT.md say a revive is an opening; scripts/smoke-links.mjs links the two new symbols to the smoke. Issue: #731 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
17b7b0ad73 |
feat(process): a red night comments on the tasks merged since the last green one (#736)
A red nightly Validate was a signal "to the author of the latest dev commits" that nobody received: the red run was visible only in Actions, and nobody computed who the author was. - scripts/night-red.mjs: acts only on conclusion=failure of the red run (cancelled, timed_out and the rest are a summary line). The last green night is the newest of the last 50 Validate workflow_dispatch runs on dev that completed successfully, was created before the red run, sits on an ancestor of the red SHA and has a green ci-proof under the release policy, so a light green run (stale) never counts. Suspects are the Issue: trailers of `git rev-list --no-merges G..R` commits that touch a class A/B file and carry no Release: trailer: docs-only and beta-candidate commits do not count, a branch merged by a merge commit brings its second-parent commits, a commit without a trailer is a "no task" summary line, an empty range means a likely flake. One comment per task names both runs, up to ten of its commits and the failed jobs, says "suspect, not guilty" and ends with the marker hp:night-red green=<G> red=<R> commits=<all sha12>. No comment goes to a closed task or to a task whose marker with the same green already lists all its current range commits: one comment per series of red nights until the task commits again; a green night starts a new series. Failures become a ::warning:: and a summary line, exit code 0. - _nightly.yml: dispatch also outputs run_id; a new job night_red runs after it only when dispatch failed with a known run, continue-on-error, permissions actions: read and contents: read (the union with the other jobs is unchanged, thin files in main are untouched), checks out dev with full history without blobs, reads Actions with github.token and writes issues with HP_PROCESS_TOKEN. The header names the addressee. - PROCESS.md §10.4: the "Красная ночь" paragraph next to the nightly ship review. Tests run the scripted rules on real git in temporary repositories with real ci-proof fixtures, and the workflow step on real bash with a local Actions API server and a fake gh. Issue: #736 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
82fbad67d5 |
fix(process): rule 10 judges the status at the commit's author date (#738)
Rule 10 compared a class A commit's authorDate with the FIRST time the issue reached S5-ready. After a return S5+ -> S3/S4 (the #726 reclassify route or a manual return) code written in S3/S4 and pushed after the new S5 passed both rules: rule 8 saw the current S5/S6, rule 10 saw the old S5 from before the return. checkCommitEraStatuses now builds status epochs from the labeled events: a label from `allowed` opens the "may touch code" epoch, S1-new.. S4-spec-review close it, every other label (blocked, track:*, review-4, S8-merged under --no-merged) changes nothing. The status at authorDate is the last status event at or before it; a pre-ready status (or no status event at all) is a rule 10 fail. Before the first readiness the old text stays; after a return the finding names the status, the return time and the next readiness or "not reached yet". Commits written before the return stay legitimate. The timeline runner, the warn without timeline or without `allowed` events and the commit selection are unchanged. PROCESS.md §10.2 gets item 10 describing the epochs. Issue: #738 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
b998b0b34a |
feat(moon): the moon with any background, and its status in General settings (#718)
The owner decided on 30.09 that the moon is not part of the "Follow the Sun" environment but a switch of its own: with a static background (global or a space's own) the card showed no moon even with the switch on, and the switch said nothing about why the moon was missing right now. With a static background there is no environment, so the moon stands in its own layer, `.hp-moon-sky`: the first child of `.stage` / `.hp-static-stage`, the whole scene, no z-index, filter or will-change, under the plan by DOM order, fading with the #101 View weight. Inside is the very #661 element, so place, size, art and fades are unchanged, and a background switch moves it to its new parent in the same render without a flicker. The phase comes from the same `resolveDayCycle`, computed only while the moon is on and on View; without `sun.sun` both cards keep their 30 s clock ticker and re-render only when the phase changes (the environment is still compared by its whole fingerprint). General settings get a second caption line under the moon switch (`data-moon-status`): one snapshot per opening, judged by the lazy chunk as if the switch were on, first reason wins (no home, day, below 3°, under 3 %), numbers rounded and clamped below the threshold they missed. `moonStatus` decides "shown" with the same `moonShownAt` as the element. It lives in a WeakMap beside the draft, so it never makes the dialog dirty; a closed opening's result is dropped. The dialog loads the chunk through the gate's loader (`withMoon`), now shared by every caller while a load is in flight, so there is still one fingerprint check and one retry token. Bundle (same build, against origin/dev): initial View 300 072 -> 300 248 B gzip (+176 B, under the 500 B of the spec; budget and ceiling not raised); lazy editor 238 558 -> 238 991 B (+433 B, the line and English strings); lazy moon 11 385 -> 11 712 B (+327 B, layer CSS and status). `src/moon.ts` stays out of the initial and the editor graph; bundle-budget now refuses an editor/moon overlap. Monolith metrics: hostRefs 4 885 -> 4 888 — the three `host.` reads of `src/editors/moon-status.ts` (hass, `_settingsDialog`, requestUpdate) through its own three-member interface, not the editor port; the other five metrics are unchanged. houseplan-editor-runtime.ts grows by two lines (import, call). Tests: AC9/AC10/AC15 and the sky layer in test/moon.test.mjs (the #661 "static -> nothing" check inverted), AC14 and the opening lifecycle in test/moon-settings.test.mjs, smokes demo/smoke_moon_static.mjs (AC1-AC6; AC1 and AC3 were red on dev) and demo/smoke_moon_status.mjs (AC11/AC12), AC7 in smoke_daycycle_layer_budget. Golden: two new scenes (static-bg-moon-gibbous-white-light, static-bg-moon-crescent-south-dark, matrix v70), the harness checks the moon's parent by background and waits for the status line in the General settings frames. Four new mutants; the clock ticker one is a browser guard (201 at the guideline of 200). Issue: #718 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
4eb712be0e |
fix(process): a workflow-permission refusal tells the author to rebase (#730)
Review r1 (Medium): refusalSummary said "повтор и ребейз не помогут" for every non-stale outcome, and since AC2 the rebase guard's summary carries it too. For a workflow-permission refusal a rebase and push by the author is exactly the way out (PROCESS.md §10.4). That outcome now says so; other GitHub refusals keep the old sentence. Issue: #730 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
562313944f |
fix(process): ship review and beta-derived pushes tell a GitHub refusal from a moved dev (#730)
After #705 and #723 two more workflow bodies still treated every failed push as a moved dev: the SHIP-REVIEW publication (_ship-review.yml) retried three times with "dev went ahead", and the derived-artifacts bot commit (_beta-derived.yml) told the release manager to rerun the workflow. A refusal by GitHub itself - a token without the workflow right, a branch rule, a hook - is cured by neither, and neither step said what GitHub answered. Both pushes now keep stderr and hand it to the #705 classifier through the same CLI (merge-candidate.mjs --push-refusal). A stale lease keeps the old behaviour: another attempt for the ship review, the rerun advice for the derived artifacts. Any other outcome stops the step at once: the log gets the git answer and the step summary gets the reason and the git answer without secrets (--summary, refusalSummary with the new ship-review and beta-derived labels). The classifier comes from dev, as for the other steps of these bodies: both jobs check out dev, and the ship review resets to origin/dev before every attempt. The ship review commit message is built line by line into a file instead of a heredoc, as in #723. The thin callers ship-review.yml and beta-derived.yml are untouched. The rebase guard in _process.yml also writes the refusal reason to its step summary now (--summary, label "rebase"); a stale lease writes none. test/publish-push-refusal.test.mjs runs both steps as they are with real bash and real git in temporary repositories (moved dev = a real neighbour push, GitHub refusal = recorded stderr with a token, a credential URL and an Authorization header); on the old bodies 10 of its 12 new tests fail. The #705 execution tests of the rebase guard in rebase-generated.test.mjs now also read the step summary. PROCESS.md names the two steps next to the Issue: #730 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
e58d7d06f8 |
feat(process): nightly ship batch review, reused by the beta gate by patch set (#727)
Ship tasks merge without a model review and their code was first read by the batch review right before a beta: one session over the whole range, ten to forty-five minutes on the release path, days after the merge. The gate also knew a single document (SHIP-REVIEW-<tag>.md) and covered tasks by number only, so a commit that landed after the review under the same trailer still counted as read. - scripts/ship-review.mjs: the patch set of a task is the sorted `git patch-id --stable` of its range commits, without `Release:` commits (the beta candidate carries every Issue: of the line) and commits touching only docs/reviews/**; the diff options are explicit so a local git config cannot change it. shipCoverage rates every ship task from the documents of the same base (candidate and origin/dev, latest publication wins): clean, high, stale, none; documents without `patches` cover by number. `tag=nightly` is a reserved mode: the candidate is required, the document is SHIP-REVIEW-<base>-dev-<sha12>.md, only none/stale tasks are read and nothing runs when nothing is uncovered. The beta reads the same delta (force=true reads everything, as before); the brief names what the night already read. The gate refuses none/stale with the command and keeps the High refusal with force=true; all clean passes without a tag document. The machine block gains `mode` and `patches` at its end. comment-high writes one line per task of a nightly document with High, once per document (hp:ship-review-high). - _ship-review.yml: prepare refuses nightly without a candidate before defaulting to the dev tip, computes the document from base and SHA and no longer reads a prepare failure behind `| tee` as "no ship tasks"; publish takes mode and patches from prepare, never from the model result; a new step comments High at night with HP_PROCESS_TOKEN. - _nightly.yml: the Validate run SHA is a separate step output before the wait; a new job dispatches ship-review.yml -f tag=nightly on it whatever Validate's outcome, waits only for the run to appear and never colours the night. Thin files in main are unchanged. - reviews-index/reviews-archive: the nightly name is a ship document with nightly: true; a beta base archives with its line, a stable base with the nearest archived line newer than the base, or stays. - PROCESS.md §11.7, §10.4 and REVIEWER.md describe the nightly mode, patch set, coverage and beta delta; the digest test pins the key rule. Tests run the prepare, publish and comment steps and the nightly steps on real bash with real git in temporary repositories; only push transport and gh are faked. Issue: #727 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
235f60e693 |
perf(card): a floor switch stops forcing layout and re-walking the config (#725)
Profiling #694 found three costs on every View pass, paid even with the summary panel hidden. The summary panel read the safe-area probe's computed style in layout(), which the card reaches up to five times per render (renderControls, menuItems, renderPanel twice, the clock check), and its updated() measured the stage, probe and kiosk buttons after every DOM commit. The insets now live in the measured state: measureLayout is the only method that reads style or layout, and updated() calls it only when an input of the measurement changed (probe, kiosk buttons or stage element, title, language, mode, kiosk, kiosk scale, narrow, HA theme), after connect() or an identity change, on visibility, once after document.fonts.ready, and from resized() as before. A floor switch or an HA tick no longer measures. The _model getter rebuilt the config fingerprint (a walk over every space and room with JSON.stringify of room settings) on each of its dozens of reads per render. ConfigFingerprintPass remembers the whole cache key (epoch and fingerprint) from the start of willUpdate() to the end of render() while the epoch, the config object and its spaces array are unchanged. Remembering only the fingerprint and concatenating the key on every read was tried first: in 2.5D on the large house the switch cycle measured slower than without any memo, and CPU profiles showed several times more garbage collection on load and on the first visit of a floor; one remembered key per pass has neither. Outside the pass (handlers, updated(), timers) every read still builds the key, so an in-place edit without an epoch bump stays visible (HP-1454-04). No write to the fingerprinted fields is reachable from willUpdate() or render(). _isoScene read the stage box during render only to feed an aspect into the overlay fit, whose frame has not depended on the aspect since #713. It now uses the frame's own aspect and passes stageSize: null. render-layout-read.mjs now also judges _isoScene and the whole summary runtime except measureLayout, forbids layout property reads (clientWidth, offsetTop, ...) besides the two calls, and reports every violation. Two registered mutants restore the old reads. No visible change: panel caps, side, offsets and kiosk clearance are computed from the same values; the 2.5D frame is the same. Issue: #725 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
d327ec3d93 |
feat(process): a failed show verdict re-routes to ask without a fresh budget (#726)
A non-green show verdict that found "something to decide" went down the same path as "fix the code": S6 with a limit of 2. Promoting the task to track:ask was left to the agent's memory, with no named criterion and no trace, and the exhausted budget only surfaced on the next S7 - after a fix nobody would read. The structured verdict now carries `route` (fix | reclassify) and an optional `criterion` (one of the six show criteria of PROCESS.md section 5). The trust boundary reads a missing route as fix, rejects one outside the dictionary and rejects reclassify on a green verdict. `reviewRoute` in process-track.mjs is the single decision: on a code review of an unconfirmed show it moves the task to track:ask and S3-spec; on an owner-confirmed show it adds `blocked` and asks the owner; anywhere else reclassify degrades to fix with a note. The verdict that spends the last cycle sets review-4 at once; the stage budget is shared across tracks, so promotion changes the limit (4), not the count. The "Решение по вердикту" step makes one `process-track.mjs route` call (from dev, like the track step) and only executes its output: comment from a file, labels from add/remove lists, status via status-label.mjs as before. The track step also emits `confirmed` and a `route_note` for the review prompt; the review document anchor gains a route tail that the old reader still parses; wait-verdict reports the two new pipeline comments. The guard's own spent >= limit check stays as the safety net. Issue: #726 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
0cc7c60e8b |
feat(process): process metrics by track — segments, returns, ship findings, job minutes (#728)
The weekly report could not say whether the tracks of #695/#696 paid off: it read only the first S4/S5/S7/S8 placements of closed issues, no track, no waiting, no reason for a return, and the CLI never passed jobs, so the "Job-минуты" line never printed. The owner decides on these numbers, so the definitions are spelled out in the report headers and anything unknown is printed as such. scripts/process-metrics.mjs (pure functions over the snapshot): - K1 trackAt/trackPath: track at a moment from the labels set before it, resolved by process-track.mjs (labelTrack) — one rule with the pipeline; infra = no class A file in the issue's commits (Release: commits aside). The issue's track is the one at its first S8-merged. - K2 issueSegments: queue/spec/work/review/rework/blocked from the first status label to the first S8, summing to lead; blocked is taken out of the segment under it; S7 over S7 is neither a return nor a new segment. - K3 returnSignal/returnReason: S7 -> S6/S3 and S4 -> S3 returns, reason from the last comment with a sign between the review placement and the return. merge and the "not run" family come from PIPELINE_EVENTS, the verdicts from verdictDeclaration with the issue's own document; the two continuations have no pipeline constant, so NOT_RUN_VALIDATE_RE and NOT_RUN_CONFLICT_RE are exported copies held by a contract test on the _process.yml templates. Anything else is unknown; hp:route (#726) gives reclassify/owner-question when present. - K4 shipFindings: High/Medium/Low of SHIP-REVIEW-*.md (docs/reviews and legacy/reviews) by the anchor block, summed per issue; the track table counts each document once. - K5 stageMinutes: jobs of process and Validate runs (skipped runs aside, at most 600, "усечено: N из M" beyond), stages by job name, per track at run time, Validate per event; unavailable jobs are "нет данных", not 0. jobMinutes gets the same data and prints again. - K6 tokenUsage: "Токены: нет данных (…)" until the pipeline records usage (issue F); the hp:usage line format is provisional. - K7 compareCohorts: issues with the first S8 within 28 days before and after 2026-09-28 (--compare, --compare-days), cohort = track x volume bucket (<=30/31-200/201-1000/>1000 lines of Issue-trailer commits without Release:, class D and docs/reviews/**); n < 3 on a side is "мало данных". - fetchSnapshot: issues state=all since the earliest window (the old selection is still "closed in the window"), timelines up to 10 pages (beyond: "таймлайн усечён"), jobs, ship and usage review docs, git log --numstat of origin/dev. _process-metrics.yml: full history (fetch-depth: 0) for K1/K7 and a 30 minute ceiling. The thin process-metrics.yml is unchanged. PROCESS.md §5 points at the report. Old sections and their tests are unchanged. Issue: #728 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
6f17b6cd4c |
perf(card): a floor switch stops re-querying the same subtrees (#694)
A floor switch replaces the whole stage, so the card's pointer-hover MutationObserver receives hundreds of records whose targets are the same few containers. Each record re-ran `matches` and a `.devlayer` subtree `querySelector` on its target, and kept doing so after the device layer had already been found. The batch logic moves to `deviceLayerMutated` in device-hit-owner.ts: a node is checked at most once per batch, the first hit ends the checks, and every added node still goes through `_syncPointerHoverSubtree` in record order. The card shrinks by 12 lines. The View stair layer read the card's `_model` getter once more for every navigable stair; the getter rebuilds the config fingerprint on each read. `renderLayer` now reads it once. `languageRenderGate` wrote `lang` on the host on every render. It now writes it only when the value differs (language switch, English fallback, a foreign value); an unchanged value is left alone. No behaviour changes: DOM, tooltips and pixels are the same. Unit tests count subtree queries per node, `_model` reads per render and `lang` writes; one mutant per change restores the old behaviour. Issue: #694 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
1d51beade1 |
feat(process): risk by changed hunks decides ship and informs show (#707)
The ship limits count lines and files but not what was touched: a 12-line pointerdown handler passed them like a typo and merged unread. The track rule also lived twice - the guard computed the cycle limit in bash while process-track.mjs computed the track, and the two disagreed on multiple track labels. The packet still told authors to rebase show/ship branches that merge cleanly. - scripts/change-risk.mjs: one pure classifier over `git diff -U0` from the merge base. Class A lines only; comments, blank lines and pure renames give no risk; deletions do. Area and token rules per class (geometry, touch, migration, devices, perf, ux, visual render/ui), evidence as path:line, five per class. - process-track.mjs: owner confirmation is a comment line "Трек: <x> — решение владельца" by the repo owner (latest wins, only for the current track); several track labels read as the strictest with a warning; cycleLimit, guardLimit and rebaseBeforeReview are the single source. `stage` makes the whole S7 track decision in one call: ship with risk and no confirmation is raised to show with evidence, a confirmed ship keeps merging without the model and records the risk for the batch review; show/ask get a risk note for the reviewer. - _process.yml: the guard asks process-track.mjs for the limit and keeps no track logic; the track step calls the script once and only executes its raise flag and comment file; risk_note reaches the Review prompt, ship_risk reaches the hp:ship-merge comment (marker line unchanged). - task-packet.mjs: track basis, limit and rebase policy; next step without the stale rebase line; risk with its consequence per track; required checks with reasons (ci:golden only on render risk); changelog and visual evidence - from the same exports. - ship-review.mjs: the batch brief prints the risk line of a ship merge. - Canon: PROCESS.md §5, §5.1, §10.4, §11.7, both digests, AGENTS.md. - Registry anchors that watched the moved code are moved, not dropped. Issue: #707 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
40607aa37f |
fix(scripts): isMainModule compares real paths of argv and module (#733)
process.argv[1] keeps the path as typed, so a script started through a symlink (or from a symlinked directory) still carries the link path there, while Node builds import.meta.url of the main module from the real path. The two never matched, and every CLI guarded by isMainModule silently did nothing and exited 0. Both sides are now resolved with realpathSync before the pathToFileURL comparison; a path that does not exist is compared as is, without throwing, exactly as before. The unit test writes a CLI and a module it imports into a temporary directory, launches the CLI directly, through a directory link (a junction on Windows, no admin rights needed) and through a file symlink (skipped on EPERM), and checks that only the launched script runs its main. It is red on the previous implementation. Issue: #733 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
5f8e8ca7e8 |
refactor(iso): drop the 2.5D overlay data nothing reads (#724)
After #714 the 2.5D overlay scene still carried what decides nothing: - src/iso-overlays.ts: IsoOverlayPlacement loses tether and grounding (always invisible) and raisedScene (always equal to visualScene); IsoOverlayOwner loses area; IsoOverlayPlacementInput loses hovered, focused, selected and filtersSupported, which the resolver ignored. IsoWallSilhouette and tetherGeometry go with them. - src/iso-scene-render.ts: the structural scene no longer projects wall silhouettes (isoWallSilhouettesOf and IsoSceneCacheEntry.wallSilhouettes) that served only as a cache key. The placement and render-scene caches are keyed by the wall geometry the scene is drawn with (IsoOverlaySceneInput. structure = scene.geometry): the structural LRU hands out the same object across zoom, stage resize and HA state, and a new one after any wall, room or opening edit. The resolveCollisions flag and its fit/live cache slots are gone: since #713 both held equal placements, and 2.5D renders only in View, where the fit probe and the live frame ask with the same devices, so they now read one snapshot. - src/houseplan-card.ts: the fit call passes no flag; the overlay scene gets structural.geometry. data-hp-iso-nudged stays the constant "false" read by the golden requireOneRise preflight, the live-touch smoke and the benchmark. Tests: iso-overlays pins the placement fields; iso-scene-render builds the structure with buildIsoWallGeometry, the #714 zoom/resize and #711 state tests stay, fit and live are asserted to share one snapshot, and two #724 AC2 tests run the production path (createIsoStructuralSource -> resolveIsoScene -> buildIsoOverlayRenderScene): a thicker wall with the same room rebuilds the scene (red with a key without walls, e.g. keyed by the room rows), and a room edit that moves the owner gives the new owner (red with a constant key). The silhouette-construction test goes with the construction. Mutants: #473 W2 (iso-placement-cache-survives-silhouette-change, id kept for history) now keys the placement cache by a constant instead of input.structure and its guard also runs the #724 AC2 tests; W6 patches the new structure line; the W5 description no longer speaks of a nudge. The isometric-contract regex checks the new key instead of the silhouette construction. docs/ISOMETRIC.md names the key. Live 2.5D output is unchanged: the 21 isometric golden scenes pass on the accepted baselines. Issue: #724 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
0d85807157 |
fix(process): publish steps tell a GitHub push refusal from a moved branch (#723)
Two steps publish a commit and treated every failed push as a moved branch: the release review job (release-review.yml) retried three times with "dev went ahead", and the review document step (_process.yml) rebased and pushed again. A refusal by GitHub itself - a token without the workflow right, a branch rule, a hook - cannot be cured by a retry or a rebase, and the step never said what GitHub answered. Both pushes now keep stderr and hand it to the #705 classifier through the same CLI the rebase guard uses (merge-candidate.mjs --push-refusal). Only a stale lease (rejected / fetch first / stale info) keeps the old retry or rebase. Any other outcome stops the step at once, without retries: the log gets the git answer and the step summary gets the reason and the git answer, both passed through redactSecrets (token, credential URL, Authorization). The review document step takes the classifier from dev, as the rebase guard does: a task branch behind dev may not carry it. The summary text is written by the new --summary option (refusalSummary), not by a multi-line string in run:, and both commit messages are now built line by line into a file instead of a heredoc (PROCESS.md §10.4 item 4). release-review.yml is dispatch-only and is not mirrored to main. PROCESS.md names the rule next to the rebase guard; the #638 trailer witness in test/release-review.test.mjs follows the line-by-line message. test/publish-push-refusal.test.mjs runs both steps as they are with real bash and real git in temporary repositories; only the push transport is replaced: a moved branch is a real neighbour push, a GitHub refusal is a recorded stderr carrying a token, a credential URL and an Authorization header. On the old steps 9 of its 11 tests fail. Issue: #723 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
d11ad9c1c2 |
ci: register ship-review and beta-derived as thin callers in main (#716)
`workflow_dispatch` runs the file from the chosen ref, but GitHub lists a workflow and accepts a dispatch (button, `gh workflow run`, API) only when its file exists on the default branch. `ship-review.yml` (#696) and `beta-derived.yml` (#697) lived only in `dev`, so neither could be started at all, and the comment "the file runs from `--ref dev`, no mirror in `main` needed" was wrong. Both beta steps are needed before the next promotion would bring them to `main`. They now follow the #623 layout instead of a full copy in `main`: a thin caller (trigger, dispatch inputs, run-name, permission ceiling, concurrency) calls `_ship-review.yml` / `_beta-derived.yml` at `@dev` with `secrets: inherit`. A full copy would either need a mirror on every edit or drift silently, and a dispatch from `main` (the button's default) would run the stale copy; the thin caller runs the dev body from any ref. The caller ceiling is the union of the body jobs' permissions (#556): ship-review `contents: read` + `issues: read`, beta-derived `contents: read` + `actions: read`; writes to `dev` stay with HP_PROCESS_TOKEN as before. `workflow_sync` in validate.yml now compares eight files, and test/default-branch-workflows.test.mjs lists the two dispatch-only files explicitly with the reason checked (only `workflow_dispatch`). Workflow tests and the #697 provenance mutant read the bodies. PROCESS.md §10.4, §8 and §11.7 say how these are run and that a new thin file is mirrored into `main` before it is merged into `dev`. Issue: #716 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
7574518575 |
refactor(iso): remove the dead #651 overlay placement search (#714)
Since #713 every raised device tile and lock badge is its floor anchor lifted by one shared wall-top rise and room names stay on the floor, so the live scene no longer called the #651 search. What was left of it only cost code, build time and review attention: - src/iso-overlays.ts: resolveIsoOverlayRigidGroups, resolveIsoOverlayCollisions with their boundary-candidate machinery, the nudge search in resolveIsoOverlayPlacement (the vector to the room safe point, the near-wall test, the zoom hint), the safe point itself, the nudge/nearWall/cleared/capped and status/reason fields, and ISO_OVERLAY_MAX_NUDGE_CSS_PX / ISO_OVERLAY_SAFETY_GAP_CSS_PX. - src/iso-scene-render.ts: the zoom reuse fast path and the CSS-pixel scale it compared; a placement now depends only on anchor, owner, footprint and rise, so zoom and stage resize reuse it by signature. residualPairs is gone and the memo key is called layoutSignature. - src/houseplan-card.ts: the overlay scene no longer receives the view, the reference view or the stage rect it only fed to that scale; data-hp-iso-nudged stays as the constant "false" that the golden requireOneRise preflight, the live-touch smoke and the Stage 4 benchmark read. The #585/#651 unit tests and the seven mutants that guarded only the removed code are deleted; kept tests drop their nudge assertions, and a stage resize is now pinned as a non-layout event. docs/ISOMETRIC.md keeps #651 as history only. Live 2.5D output is unchanged: the 21 isometric golden scenes pass on the accepted baselines. Issue: #714 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
e5c217111c |
fix(process): a GitHub push refusal is not a stale lease (#705)
merge-candidate treated any push stderr containing "rejected" as a stale lease. A `! [remote rejected]` from GitHub itself - in #700 the rebased candidate changed .github/workflows/ and the conveyor token has no workflow permission (runs 36484993494, 36487044060) - became "the branch moved after the reviewed material (#312)", and the stderr was never printed, so the author was sent to look for a commit that did not exist. classifyPushRefusal now tells three outcomes apart: a stale lease (`[rejected] (stale info)`, `fetch first`, a server-side lock race) keeps the old behaviour; GitHub's workflow refusal (PAT, OAuth App, GitHub App, bot and integration wordings) and any other `[remote rejected]` get their own outcome, S6-in-progress and a comment naming the reason. The workflow comment says what to do: the author rebases and pushes, or the owner grants the permission. The git answer goes to the log and the comment with tokens and credential URLs cut out; the merge-step failure comment is redacted too. The rebase guard in _process.yml parses its push refusal with the same code (`merge-candidate.mjs --push-refusal`): a stale lease is the old error, a workflow refusal returns the task to S6 without review like a conflict, and material/reuse/gate skip the rebase that never reached the branch. Mutant push-refusal-kinds-glued restores the old regex; guard: #705 AC1. Issue: #705 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
1557475af3 |
fix(ci): stable promotion judges nothing already judged on dev (#703)
Validate on a push to main took the range base from main's own runs only, and skipped HEAD: the nearest judged ancestor was the previous stable, so the whole beta line was re-judged by today's rules (run 36468413524: 55 smoke private writes made before #629). Preflight on main used event.before, the same old-main..candidate. The range base now reads Validate runs of both integration branches, counts published release tags as judged material, and accepts HEAD itself when it already has a successful run (or a tag). A promoted SHA gets an empty range and the dev verdict; a failed HEAD is re-judged over the same range; a hotfix on main is judged from the candidate. Issue: #703 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
dca0fd2876 |
Release v1.79.0-beta.1 candidate
Issue: #661 Issue: #692 Issue: #693 Issue: #711 Issue: #713 User-Visible: yes Release: v1.79.0-beta.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
0d641ffbfd |
feat(iso): the 2.5D floor is the Flat plane, one wall-top rise for tiles (#713)
- Vertical oblique projection: the floor matrix is the identity and a height rises straight up by z·sin 20°, so the on-screen wall height is unchanged and the cos 20° foreshortening of the plan, decor and anchors is gone. - Device tiles and lock badges stand on the wall-top plane with one common shift; the #651 placement search no longer runs in the live scene (its removal is #714). Room names keep their Flat floor point. - The 2.5D fit no longer reserves the 48 CSS px nudge budget. - Switching projection keeps the camera when the previous projection was on screen: saving the setting, entering an editor from 2.5D and adopting a warm memo from the other projection re-read only the scalar zoom. A cold 2.5D start still opens the 2.5D home. - Opening faces are ordered along the oblique projector (s·y + z). Witness: demo/smoke_iso_flat_parity.mjs (AC2–AC5, AC11) is red on the old code. Issue: #713 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
c8c470ed57 |
feat(view): the moon in its phase over the "Follow the Sun" background (#661)
At dawn, dusk and night the environment shows the moon in the top-left corner of the scene, behind the plan: computed in the card from the home coordinates and the browser clock (short Meeus series + topocentric parallax, within 1.4° / 1.8 pp of JPL Horizons), one designer image under a continuous phase mask with the lit side always on the left (owner 2026-09-29), a feathered terminator, 3°/3 % thresholds and the 2 s fade of the window rays. Everything but a small gate lives in the lazy moon-runtime chunk, with its own 30 s ticker. General settings: "Sun" becomes "Sun and Moon" with one switch, on for new installations (DEFAULT_CONFIG), off for existing ones. The initial View graph sat 728 B under its budget: the gate is paid for by moving fifteen dialog-only strings of General settings into the lazy settings dictionary (#459) and by one build fingerprint literal instead of three, so the graph ends 4 B above dev. Golden: two new moon scenes, and the two General settings help frames show «Sun and Moon»; the WSL artifact test fixture now models scenes whose first capture awaits acceptance. Issue: #661 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
a372b354c6 |
fix(2.5D): a device icon never moves because its state changed (#711)
Owner's decision in #694: icons must not change position with state. Since #651 the rigid overlay layout sized a device by its value text and badges, which change with HA state: a light toggling on changed its width from 37.2 to 26.7 CSS px and re-laid out all 62 overlays of the dense scene (~385 of 495 ms of stateUpdate; v1.77.0 had 208 ms). - iso-scene-render.ts: the layout sees the state-free tile (icon at its configured size, no value text, badge or supplemental metrics). An HA-only change keeps the layout and refreshes only the visual extent that scene bounds read, without a collision search. - iso-overlays.ts: the rigid-group search skips candidates that already lose to the fallback on room, then wall violations (lexicographic bound). The result is unchanged — identical placement hash on the dense scene — at about 35 % less work. - docs/ISOMETRIC.md, changelogs; test #711; mutant iso-device-layout-follows-state-again (written, not run — #709). Local isometric-stage3-dense-v1, 3 samples: stateUpdate 522 → 89 ms (v1.77.0: 208), modelReady 3665 → 3129, switchCycle 5544 → 4700. Issue: #711 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
a8321e32cc |
process: mutants run only in the nightly full registry; speed rules for ship/show (#709)
Owner's decision 2026-09-29: mutants check the tests, not the product. During development they are not run at all — not locally, not in CI, not by the reviewer. The whole registry is the nightly run (mutation-gate.yml, #513); a survivor files an issue (#472). The #693 post-mortem: 36 of 57 minutes of a one-line fix went to optional work. - process-track.mjs: `mutants` is always false (no track, no label). - classify-changes.mjs: Validate requests no diff mutants on any event; the `mutants` input stays so old `-f mutants=…` calls do not fail. - _process.yml: the default for the gate and the merge is false. - pre-push-gate.mjs: the manual run no longer runs mutants. - Canon: PROCESS §2.7 (a mutant is written, not run; `--check` keeps the anchors), §5.1 (`ci:mutants` retired), §8 (ship/show: nothing beyond gate:small and the spec — one proof per item, no `--smokes` on ship, a stray flake is an issue, not an investigation), §10.4; AUTHOR, REVIEWER, AGENTS, TESTING. - Registry: four mutants of the old request rules replaced by dev-mutants-requested-again and track-pays-for-mutants-again. Issue: #709 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
18c9f8e77c |
fix(stairs): View shows the link pointer over a stair, not the move cursor (#693)
The Plan editor rule `.hp-stair.input-enabled .hp-stair-hit { cursor: move }`
also matched the View layer, which sets input-enabled only to receive
clicks. The hit area sits over the outline, so the link's pointer on the
group was never visible and every stair in View showed a drag it cannot do.
- src/stairs-view.ts: View stairs carry `hp-stair-view`.
- plan.styles.ts: `move` applies only without it; in View the hit area
keeps the group's cursor — pointer on a link, the stage's otherwise.
- demo/smoke_stairs.mjs: computed cursors in View (link, no target) and
in the Plan editor; the two View checks are red on the old code.
- test/stairs.test.mjs: the cascade without Chromium; mutant
view-stair-cursor-move-again.
- docs/STAIRS.md, changelogs.
Issue: #693
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
|
||
|
|
52a56430ab |
process: an unproven smoke link runs the visual minimum; raster defects need a witness (#690)
The two remaining owner decisions of #690 and the legacy trivial text. - scripts/smoke-select.mjs: VISUAL_MINIMUM, eight smokes of modes, layers and rendering (under a minute locally). An executable diff with no proven link now returns and prints it instead of only "the reviewer decides"; #687 missed smoke_modes that way (item 1'). - scripts/gate-small.mjs: `--smokes` runs the minimum with the selection. - PROCESS §7.1 and AUTHOR.md: a raster, sharpness or compositing defect needs a witness red on the old code for the owner's symptom and the owner's confirmation in a real GPU browser (item 4). - PROCESS §8, TESTING.md: the minimum in the smoke-select rule. - scripts/task-packet.mjs: legacy `trivial` is product flow read as track:show (§5.1), not a short track without a spec. - Tests; mutants visual-minimum-silent-again, visual-minimum-on-proven-link, gate-small-skips-visual-minimum; task-packet-trivial-is-product-flow retargeted. Issue: #690 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
ae0e516af1 |
process: a rereview sets S7-code-review again instead of stripping it (#706)
The label step after integration ran one gh call `--add-label "$TO" --remove-label "$FROM"`. For the rereview outcome TO == FROM == S7-code-review, and gh added and removed the same label: #699 was left without a status and no new round started (run 36491087708). - scripts/status-label.mjs: the same label is removed and set again through relabel from process-reconcile (#555), so the labeled event starts the next round and a failed restore fails the step; a different label is still one call. - _process.yml: the step calls the script. - PROCESS.md: the exact-candidate rule names the relabel. - test/status-label.test.mjs; mutants rereview-relabel-in-one-call and process-label-step-combined-again. Issue: #706 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
6a8658cab9 |
docs(release): the candidate checklist lowers the ratchets (#699 r1)
CODE-REVIEW-699-r1 M1: `node scripts/ratchets.mjs tighten` was named only by the warning `release:prerelease` prints at publication, when the candidate commit is already made. The candidate checklist in docs/DEVELOPMENT.md now runs it after `npm run bundle:release`, so the caps come from the fresh dist/ and land in the candidate commit. - test/ratchets.test.mjs pins the step and its order. - Mutant release-runbook-forgets-tighten. Issue: #699 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
2369c50607 |
process: the bundle CLI judges the beta ceiling before the budget (#699)
Validate on 3dd032d7 (run 36480911145): the mutant initial-view-ceiling-unplugged survived. With the band over the ceiling, ceiling + band (303 000) lies above the absolute INITIAL_VIEW_GZIP_BUDGET (301 066), so every value the CLI test could feed went red on the budget first and the ceiling check became unobservable. - bundle-budget.mjs CLI: the initial View ceiling is judged before assertBundleBudget, so a growth over the band names the ratchet that caught it; the budget still stops anything the band lets through. - The CLI test feeds ceiling + band + 1 and expects the band message. - The mutant's anchor follows the moved lines. Issue: #699 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
c68d92f674 |
process: ratchets get a band over the beta ceiling (#699)
Two-sided ratchets with zero slack made parallel tasks conflict on shared numbers, recompute them after every rebase and hit a ceiling because a neighbour merged first (#689 after #691). - Core lines (test/core-file-budget.test.mjs): a branch may grow up to CORE_BAND = 50 lines over the beta ceiling; shrinking no longer fails it. - Bundle graphs (bundle-budget.mjs): initial View and lazy graphs fail only above ceiling + 2 000 B; below the ceiling is not a branch finding. The absolute INITIAL_VIEW_GZIP_BUDGET stays the wall. - Monolith numbers (monolith-metrics.mjs, unused-locals-gate.mjs): METRIC_BANDS — 5 for delegates, port members and privates, 25 for host. refs, 2 000 B for dist/; a lower number is reported, not failed. - Browser mutation guards: 200 is a guideline — mutation-gate --check warns above it instead of failing; every guard still needs its reason line. - scripts/ratchets.mjs: `report [--warn]` and `tighten` — on the beta candidate the release manager sets every ceiling to the fact in one commit; release:prerelease prints loose ceilings as a warning. Canon: PROCESS.md §3 (browser guards, monolith numbers) and §8 «Храповики»; docs/TESTING.md. Issue: #699 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
f6e317d871 |
process: a failed read of open issues never files a duplicate (#700 r1)
CODE-REVIEW-700-r1 Medium: `gh issue list … || true` turned a failed read into an empty answer, and the step went on to gh issue create — a second [workflow-sync] issue next to the open one on every network or rate-limit failure. A failed read now warns and exits 0; creating stays reserved for «read succeeded, nothing open». Mutant workflow-sync-issue-duplicated-on-read-failure. Issue: #700 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
e45bc87c6d |
process: preflight does not fail a task branch for foreign causes (#700)
11 of 85 returns in #600–#691 were the thin-workflow mirror check, and any push could turn red because a foreign site behind a docs link was down. - validate.yml preflight: on refs/heads/issue/* the workflow_sync mismatch is a warning in the summary, not a failed verdict; push to dev, the beta candidate and the release keep it red. - On push to dev a mismatch opens one owner issue titled [workflow-sync] (or comments on the open one), like the nightly mutation gate (#472); preflight gets issues: write for that. - check-docs --external=warn: external link failures become warnings; the docs step passes it on task branches only. Canon: PROCESS.md §10.4 («Workflow из ветки по умолчанию»). Issue: #700 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
8dcc1cad4e |
docs(process): канон без противоречий, вход автора короче (#701)
Сверка PROCESS.md, ролевых выжимок, AGENTS.md, TESTING.md, CONTRIBUTING.md и скриптов по 26 найденным расхождениям (D1–D26): трейлеры по классам изменений, gate:small как единственный источник состава, пороги ревью, путь реестра мутантов, golden по ci:golden, порядок чтения промпта ревью. - scripts/change-classes.mjs: классы A/B/C/D — один модуль для process-gate и проверки трейлеров. - commit-msg: коммит только с файлами класса C (документация) трейлеров не требует; указанные трейлеры по-прежнему проверяются. - Маршрут автора без docs/STATUS.md: 5345 → 4703 слова. - Промпт ревью читает SCOPE → AGENTS → REVIEWER, как ROUTES.reviewer. Issue: #701 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
19dc61db15 |
process: the merge deletes the task branch it merged (#702)
370 merged issue/* branches sat on origin; the branch list stopped meaning anything and an agent looking a branch up by number could take a stale one. - merge-candidate.mjs: after a successful push to dev the task branch is deleted with --force-with-lease on the tip the merge saw last — the candidate published into the branch, or the material on fast-forward (the index commit lives only in dev). A commit that landed after the merge keeps the branch, and the merge comment says so; a failed delete never undoes the merge. Failed, stale and conflicting merges keep it. - The one-time cleanup of the already merged branches is not in this commit: the list goes to the owner first. Canon: PROCESS.md §10.4 (exact-candidate merge). Issue: #702 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
6ab791e348 |
docs(process): name today's monolith tolerance in the rebase rule (#698 r1)
CODE-REVIEW-698-r1 Medium: the canon said the merged monolith numbers are judged «by the band test (#699)», but #699 is not merged — today compareWithBaseline judges five numbers exactly and only dist/ bytes with a band. The paragraph now says so: dev's side of the baseline turns the candidate's Validate red when the task itself changed those numbers — the same return to the author as before, after Validate instead of before the review; the band for all six numbers is #699. The comment on UPSTREAM_WINS says the same. Issue: #698 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
5986332eda |
process: the rebase merges what two tasks never disagree on (#698)
14 of 48 returns in #600–#691 were rebase or merge conflicts on shared files where the two edits do not contradict each other. - .gitattributes: docs/CHANGELOG.md and docs/CHANGELOG.ru.md use the built-in merge=union driver — both tasks' lines in ## Unreleased survive a rebase, a merge and git merge-tree (#696's clean-merge test) without a stop. - rebase-generated.mjs: UPSTREAM_WINS — on a conflict in scripts/monolith-baseline.json the rebase takes dev's side; the band test on the candidate's Validate judges the merged tree (#699). Any other conflicting path aborts exactly as before, with the full list. - merge-candidate.mjs: the candidate's patch-id excludes the changelogs and the monolith baseline next to docs/reviews, so a neighbour's line next to the task entry does not re-send a green task to review. - screenshots.json needs nothing: after #697 task branches do not commit it. Canon: PROCESS.md, the rebase paragraph of the review index (#643). Issue: #698 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
2a62ad5b95 |
process: derived artifacts are accepted on dev once per beta (#697)
The screenshot fingerprint and golden baselines stop being a tax on every task branch: - Task branches no longer commit docs/images/** or golden baselines. On a branch the screenshot freshness stays a preflight warning; the review prompt, REVIEWER.md and AUTHOR.md drop check-docs as a per-task gate. - beta-derived.yml refreshes them on dev in one bot commit before the beta candidate: canonical docs capture + docs:accept --reviewed, golden from the golden-images artifact of a completed Validate on dev + golden:accept --reviewed. A changed frame or scene is accepted only when named in the inputs; undeclared differences refuse. Baseline commits carry Release: and Baseline-Reviewed:; the subject is not a candidate subject. - classify-changes: the Release: trailer on an issue/* branch no longer switches on the heavy set. ci:full / ci:golden do: process-track emits full=true, the review gate dispatches Validate with full=true and does not accept a light proof. Canon: PROCESS.md §3 п.13, §5.1, §8, §11.4; CONTRIBUTING.md. Issue: #697 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
e1ae8f4ac7 |
process: the review pipeline prices each round by track (#696)
show/ship stop paying for diff mutants and for every move of dev: - scripts/process-track.mjs resolves the track from the current labels and the diff (show for unlabelled infra, ask for unlabelled product work) and checks the mechanical ship limits; outside them the pipeline comments and relabels track:ship -> track:show in the same round. - Validate on the review material is light on show/ship: a completed push run on the exact SHA is proof, a dispatch asks mutants=false. ask and the ci:mutants label keep the mutant dispatch. - show/ship skip the pre-review rebase when git merge-tree with dev is clean; the candidate is rebased once at merge and still passes Validate before the push to dev. The light merge waits for the push run of the candidate and dispatches only when none appears. - ship inside the limits merges after the light Validate without a model review; the issue gets a machine marker hp:ship-merge. - ship-review.yml + scripts/ship-review.mjs read the code of all ship tasks of a beta range in one model session and publish docs/reviews/SHIP-REVIEW-<tag>.md; both beta publication paths refuse a range with ship tasks the document does not cover or that carries a High. - show reviews judge correctness and AC; the spec review installs neither npm ci nor Chromium, the show review installs Chromium only when the issue names a smoke. Canon: PROCESS.md §5, §5.1, §10.4, new §11.7; REVIEWER.md, AUTHOR.md and AGENTS.md digests. Issue: #696 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
f6c76b7351 |
process: infrastructure without a track label runs as show (#695 r1)
CODE-REVIEW-695-r1 Medium: PROCESS §5.1 says an infrastructure task (§1) without a track label reads as track:show, but neither the pipeline guard nor the task packet did that. - _process.yml guard: with no track:* and no small/trivial label, the diff of the task branch against dev (compare API) with no class A file gives the show cycle limit 2. A truncated compare answer (300 files) proves nothing and keeps the limit 4. - task-packet.mjs: an infrastructure packet names the track it runs on: «инфраструктурный · show» without a label, the owner's label otherwise. - Mutants guard-infra-keeps-ask-limit and packet-infra-track-ignores-show-default. Issue: #695 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
57ce10721f |
process: tracks ship/show/ask are set by the owner's label (#695)
The analysis of 85 closed tasks #600-#691 showed that the light track cost as much as the full one (115 min and 12 events vs 102 and 13) and that the owner had no label to choose the route. The owner accepted the proposal on 2026-09-28. - PROCESS §5 is the track table: track:ship (S1 -> S5, one line under "## ТЗ", <= 30 src lines, batch review before the beta), track:show (default, S2 -> S5, up to three AC, no spec review, 2 code cycles), track:ask (full route). The owner's label beats the criteria, which become a hint; any agent may raise a track, only the owner lowers it. - §5.1: ci:full / ci:golden / ci:mutants order heavy checks on any track; small and trivial read as track:show, no label as track:ask, an infrastructure task as track:show. - §2, §2.2, §2.4, §2.5, §4, §7.1, §7.2, §9, §11 follow; AUTHOR/REVIEWER digests and AGENTS.md follow with the digest test and its mutants. - task-packet.mjs reports the track via trackFromLabels(); the pipeline reads track:show/track:ship for the cycle limit of 2 and lets an explicit track:ask win. Pipeline behaviour by track is #696. Issue: #695 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
600330e187 |
fix(daycycle): stage-sized plan layers without a frozen raster; revert #685 hatch
With the day/night background the plan went blurry after zooming from 100 %
(sharp when the page was opened at 800 %) and navigating a strongly zoomed
plan flashed the page white. Both came from #582's composition, not from the
wall hatch that #685 replaced:
- `.stage.daycycle.hp-safe-daycycle-outline .plan-svg` promoted the scene
with `will-change: transform`; Chromium freezes the raster scale of such a
layer, so the 100 % raster was shown stretched. The explicit layer #582
needs is now `will-change: opacity` (re-rasters at the current scale).
- The filtered outline had `overflow: visible` and a gesture exposed every
scene (#544) without bound, so the promoted layers grew with zoom squared
(CDP LayerTree, ~460 %: plan-svg 15.9x, outline 13.2x the stage; 39x after
navigating at 800 %). The full card clips its outline to its box and marks
it data-hp-live-overflow="clip" (never exposed); the live viewport bounds
every other exposure with an inline clip-path: inset(-25%) that leaves
with it, so idle DOM stays byte-identical (#531).
Owner-verified in Chrome 152 (built-in browser, DPR 2): sharp after 100 ->
800 %, no white flashes after reloading at 800 %.
Owner decision: #685's analytic gradient is reverted (
|
||
|
|
53503e0373 |
chore(metrics): accept touch controller bundle growth (#691)
Issue: #691 User-Visible: no |
||
|
|
37fbb9824d |
fix(touch): separate pan, edge swipe and double fit (#691)
Issue: #691 User-Visible: yes |