mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
bf0bc1f4a6f588f79df265f37f25f876773caeb0
676
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b7dbc21c42 |
fix(process-metrics): task volume, merge return reasons and spec drafts (#752)
The weekly report disagreed with its own definitions (#728) and with the pipeline texts that appeared after it (#705, #723, #729). Volume. A task's volume counted documentation, so the archive move of #682 weighed 333 060 lines and #680/#681 thousands, all landing in the "> 1000" bucket and shifting the cohort medians; and every commit with a Release: trailer was dropped, including the task's own golden acceptance and test re-pinning commits. Volume is now the +/- of class A and B files only. Only beta commits are left out: the beta or release candidate (Release: trailer plus the candidate subject or a changed bundle, bundle-policy.mjs, drops every Release: commit, so it is not reused. A task whose commits touch only docs/reviews/** has no volume and is no longer read as infrastructure: the pipeline writes those documents, not the task. Return reasons. Every non-merge outcome of merge-candidate.mjs fell to "unknown". merge-candidate.mjs now exports a sign for the heading of each outcome comment (OUTCOME_SIGNS; the step-failure text moved into commentFor as 'error', byte for byte), and a test on the templates themselves holds every case to its own sign. The report maps merge-stage outcomes after a green verdict to "merge" and a push refused while rebasing before review to the new "push-refused" reason. Spec drafts. A new section after "По трекам" counts the S4-spec-review epochs on the ask track for tasks whose first S5-ready falls in the window, the epochs with a "Черновик:" comment (§7.2) and whether the draft went to S5 or was thrown at S3, Spec-Draft: commits in dev for the window, and S5 -> S7 per track for tasks with and without a draft, "мало данных" under three. The snapshot also reads timelines of tasks in S5-S7. Three #728 assertions pinned the old behaviour (a Release: fixture without the candidate subject, and the rebase workflow refusal read as unknown); they now expect the new definitions. Issue: #752 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
a22f6f5411 |
fix(process-metrics): count model tokens only for the report window (#761)
tokenUsage summed the usage line of every review document in HEAD: the report had no window, and every week repeated the whole history. A document now enters a week's tokens when the commit that added it to dev falls in [since, until]. fetchSnapshot reads the committer date from git log -M --diff-filter=AR over docs/reviews and legacy/reviews: an add sets the date, a rename (the #682 archive move) carries it to the new path instead of adding the document again. The "missing" count of #737 (hp:usage-none) follows the same window. ship findings keep reading every SHIP-REVIEW document; only the token sum is windowed. Without the date map (a unit over ready documents) there is no window, as before. Proof is a temporary git repository with dated commits: a document outside the window, one inside, an hp:usage-none pair on both sides and an archive move inside the window; only the inside documents count. Issue: #761 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
25001ef7ab |
fix(ci): pipefail before every | tee, API base from GITHUB_API_URL (#751)
No workflow sets `shell:`, and GitHub runs such a step as `bash -e {0}`,
without pipefail: the exit code of `… | tee` is tee's, and a failing left
side passed silently. Three steps were unprotected:
- _process-resume.yml: an exception of process-resume.mjs (gh, API) left the
step green and the resume event was lost until process-reconcile;
- release-review.yml: a failed `prepare` went on with an incomplete
GITHUB_OUTPUT and proceed=true;
- validate.yml: a failed `classify-changes.mjs --heavy` left `heavy` empty,
heavy jobs were skipped and job `changes` stayed green.
Each gets `set -o pipefail` as the first line of `run` (validate.yml's step
becomes a block), following #727 and #472. test/workflow-pipefail.test.mjs
walks every .github/workflows/*.yml: a `| tee` line in `run` must follow
`set -[a-z]*o pipefail` or the step must have `shell: bash`; on the old tree
it names exactly the three places, and the _process-resume and validate
steps run on real bash under `bash -e` with a failing node.
ci-proof.mjs exports githubApiBase(env) (GITHUB_API_URL or
https://api.github.com, no trailing slash); githubCandidateTree,
loadGithubProofContext and release-gate's workflowRunsUrl take `apiBase`
with that default instead of the hardcoded host. night-red.mjs passes the
base directly and drops the fetch wrapper that rewrote the prefix. On
github.com the runner's GITHUB_API_URL is the same host, so behaviour there
does not change; archive_download_url stays as the API returned it.
The `mode` input for ship-review is out of scope (thin file in main, #716).
Thin files are not touched: _process-resume.yml is a body, validate.yml and
release-review.yml are not thin.
Issue: #751
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
|
||
|
|
4aa6c85f98 |
fix(process): the #562 infra entry no longer covers an issue in S3/S4 (#753)
Rule 8 skipped the status check for any range without class A files, so a task in S3-spec or S4-spec-review could push a branch of tests, demo or scripts with only a warning. §11.8 forbids exactly that: before S5 neither class A commits nor the branch itself is pushed, because the spec-review step takes the freshest origin/issue/<NN>-* as its material and lays the SPEC-REVIEW document there, putting code in front of a spec reviewer who must not read it (§2.4). The #562 entry was written for a task before its first S status. The decision is now made per issue in checkIssueStatuses: the status stays optional only when the range is infrastructural and the issue carries neither S3-spec nor S4-spec-review. Such an issue gets a rule 8 refusal naming its status and §11.8; the range-wide #562 warning is still printed. No status, S1-new/S2-analysis (reviewer-filed infra issues) and S5-S8 keep their old outcome; closed, blocked and fail-closed checks are untouched; rule 10 is still called only for ranges with class A. PROCESS.md §10.2 gets the one-sentence exception, the mutation registry a mutant that drops the S3/S4 condition. Issue: #753 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
f0e1c0b8cc |
fix(warm): revive the editor draft when write access arrives late (#756)
A same-route remount that cannot edit yet - hass arrives after the element is inserted (the demo's own order), or a non-admin waits for the server's can_write - keeps the editor in _pendingNavMode and enters it later through _resumePendingNavMode -> _setMode. The draft revival was wired only into the immediate warm adoption (_requestMode(..., adopt)): on the pending path the draft was lost, _warmRevivePending stayed up for the instance's life, _warmSnapshot stopped writing dlg, and the next remount brought back the predecessor's stale draft. The adoption tail (draft revival once under a held refit, then the settled stage as the refit baseline) is shared by both paths: the sequencing lives in src/warm-mode-adoption.ts, the refit bookkeeping in the card's _holdWarmRefit/_releaseWarmRefit. The pending mode still enters through _setMode, the transition authority smoke_nav_persist holds it to; resumeWarmMode then settles the revival. _setMode ends the passive boot grace and refits the camera to a header measured before the editor chrome rendered, so a camera the pending window left untouched is put back and held exactly as an immediate adoption holds it; a camera that has already moved on (View refit, the user's pan, another space) is left to the ordinary refit. A mode that did not commit, an explicit mode command in the pending window and a route departure settle the revival too: no outcome leaves _warmRevivePending up. The core file gives back 4 lines. smoke_warm_dialogs gains section H through the UI: the three late-write orders keep mode, draft, dirty baseline and a frame-by-frame identical viewport; the chain carries this instance's draft, not the predecessor's; a space switch in the pending window eats the draft. 14 checks are red on dev. The mutant warm-pending-mode-leaves-revive-waiting is guarded by the smoke; docs/WARM-REMOUNT.md §2 describes the pending editor. Issue: #756 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
86a301f618 |
fix(ci): the screenshot fingerprint is only a warning on a task branch (#760)
The pipeline dispatches a full Validate for a `ci:golden` task, and `screenshotsGateMode` read `full=true` as strict on any ref. Between betas the source fingerprint on dev is legitimately stale (#479: re-captured for the beta candidate), so every visual task failed preflight on the conveyor's material until its author re-ran `docs:accept --identical` on the current dev - #718 and #740 both did, and two visual tasks in a row could not merge without it. On a task branch the mode is now `warn` even with `full=true`; dev, main, PRs, the schedule and Release: candidates stay strict. Issue: #760 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd |
||
|
|
dc77852e0b |
fix(space-card): key room shapes by space and id so a config change never repaints a stranger (#745)
The space card drew its rooms with a bare map(), so Lit reused room nodes by
position, and `.room { transition: 0.12s }` (planStyles is part of this card's
styles too) drew a node's fill and stroke in from whichever room held it
before. Two paths change the room set in the same DOM: a new `space` in
setConfig of the same element (the card editor's preview), and a config event
from any device that inserts, removes, reorders or re-zones a room of the
shown space. Filled rooms faded out and back in for ~0.12 s, unfilled ones
briefly darkened in a filled room's place.
The list is now keyed(space.id, repeat(rooms, (r, i) => r.id || i, ...)),
the same shape as the full card after #742: the outer key handles the space
change, the inner one keeps a node bound to its room inside a space. An
id-less room keys by its numeric index, which never equals a string id. The
transition itself stays: it smooths a real fill change on the same room. The
#742 note in plan.styles.ts now names the space card as well.
Witness: a new section of smoke_space_card. The config is delivered by a
server push (__hpTest.setServerConfig), the event the card subscribes to.
Red on dev: node r1 reused for g1 with fill/fill-opacity transitions and a
first-frame fill of rgba(0, 0, 0, 0) / 0; a room inserted first shifts all
four nodes and replays fill transitions. A real custom_fill change still runs
a fill transition on the same node (catches `transition: none`). One mutant:
inner key replaced by map(), guarded by AC2 (checked by hand: red).
Issue: #745
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
|
||
|
|
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 |