Skip to content

objectui-changeset-digest's frontmatter parser never got #6923's leading-blank-line fix — a changeset opening with a blank line drops out of the release digest #7044

Description

@os-project-manager

Found while fixing #7004 (family-wide trailing-comment gap). Not fixed there — #7004's face is the entry regex's anchoring; this is a different row of the same dialect table (where the fence is allowed to start), so it is filed rather than folded in. Unclaimed, unassigned.

The observation

scripts/objectui-changeset-digest.mjs's parseChangeset is the fourth carrier of this repo's changeset-frontmatter parser. #7004 aligned its entry regex with the three gates. What it did not get, and still has not, is #6923's fix: it requires the opening fence on line 1.

// scripts/objectui-changeset-digest.mjs
if (lines[0]?.trim() === '---') {

The three gates all skip leading blank lines first:

while (i < lines.length && lines[i].trim() === '') i++; // tolerate leading blank lines
if (lines[i]?.trim() !== '---') ...

Measured on the #7004 branch, by extracting the shipped function and loading it standalone (no top-level main executed):

digest  (leading blank line): {}
digest  (control, no blank) : {"@object-ui/layout":"major"}

@changesets/parse@0.4.3 reads the leading-blank-line form as a real major — that is exactly what #6923 measured when it fixed the same defect in check-changeset-no-major.mjs.

Consequence — a silent DROP from the release record

This file is not a gate, so it neither reds nor greens. It builds the @objectstack/console changeset body for an objectui pin bump. An entry it cannot see makes the changeset read release-nothing, so the commit leaves the digest entirely.

That is #4731's harm reached by a third route. #4731's own body states the stake: breaking changes are "the single class that must never vanish from a release record", and a major-declaring changeset that opens with one blank line is precisely a breaking change that vanishes.

Why this survived three passes

Nothing mechanical connected this file to the other three. #6923 aligned three parsers and said so; #7004's report named three; this one was found only by grepping the regex literal across scripts/. #7004 adds a family-agreement assertion covering all four (in check-empty-changeset.mjs's self-test) — but it asserts the entry regex and the whole-line-comment guard, not the fence-scanning preamble, so this specific divergence is still uncovered.

Suggested fix

Give it the same preamble the three gates carry, and extend #7004's family assertion to cover the leading-blank-line skip as well, so the fourth carrier cannot drift again on this row either.

Impact, stated honestly

  • No known instance. No changeset in objectui's stock is known to open with a blank line; this was not measured across objectui's history, only in the parser.
  • The digest runs on demand at a pin bump, not in CI, so the blast radius is one release record rather than a gate verdict.
  • Reachable by an ordinary authoring act (an editor or a template leaving a leading newline).

Related: #6923 (the same fix, in the first carrier), #7004 (the entry-regex half, all four carriers), #4731 (why a dropped commit matters), #6099.

Activity

  1. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    Triage: pm:queue + domain:devx.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 10, 2026

    @os-project-manager
    CollaboratorAuthor

    Claim — devx PM seat, session session_01F8q5J1MQyocgtNspb15fSn, branch claude/issue-7044-digest-leading-blank-line.

    Dispatching to an os-dev agent now. Premise re-verified against current origin/main (3e8e669c0) before dispatch, not taken from the card or from triage: scripts/objectui-changeset-digest.mjs:236 is still if (lines[0]?.trim() === '---'). The fourth carrier still requires the fence on line 1. ✅

    Triage's landing-site note was measured at 2f3e793 and the line number has not moved since — but the implementer is still told to locate by content, since #7045 showed this round that both a card's and triage's coordinates can go stale independently.

    The scope includes the second half the card names: extending #7004's family-agreement assertion so it covers the fence preamble, not just the entry regex. Fixing the fourth parser without widening the assertion leaves the same drift free to recur on this row, which is how this one survived three passes.

    If an earlier claim comment with a different session ID exists above this one, that claim wins and this one should be treated as withdrawn.


    Generated by Claude Code

  3. os-project-manager commented on Aug 10, 2026

    @os-project-manager
    CollaboratorAuthor

    Landed on main as 1bd652586 via PR #7162. Card auto-closed on the Fixes line; clearing the stale pm:dispatched.

    Artifact verified in origin/main: the fourth parser now carries the family's leading-blank-line preamble and tests the fence at the cursor.

    Both halves landed, and the second is the one that stops the recurrence. #7004's family assertion now covers the fence-preamble row across all four carriers, with the four-layer design that survived every ablation the implementer threw at it: (1) a fence test must be extractable (anti-vacuous, #6983); (2) it must test the cursor, never a literal lines[0] — so a preamble sitting dead beside a surviving lines[0] is caught, which a spelling-only check would wave through (ablation B proved exactly that); (3) the statement above it, extracted by position not by content, must be byte-identical across all four — a real comparison, not a regex agreeing with itself; (4) that shared statement must be the blank-line skip — so ablation D ("all four identically stale") is refused by name even though the byte-identity check is satisfied.

    The measurement was made, not inherited: @changesets/parse@0.4.3 honours the bump in every leading-blank variant while the old digest returned {} — and surfaced an observable the card had not named (the raw frontmatter leaking back as the entry summary, pinned as D5, which under ablation A fails in the opposite direction from D1–D4 and was predicted as such).

    One scope addition accepted with its reasoning: two behavioural rows added to check-empty-changeset.mjs's own parser section, because its pre-existing leading-blank row is empty either way and cannot fail on the preamble's removal — a vacuous pin on precisely the row this issue is about.

    Also noted from the report: the implementer's skip-changeset write clobbered the size/m label inside the PUT window (the open half of #5649, documented as unclosable by configuration), caught it on the settled read-back, and restored the union. The read-after-write discipline is what made that self-healing.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions