Skip to content

Semantic diff: write and draw changes as semantic objects - #8908

Merged
cristianoc merged 1 commit into
masterfrom
cristianoc/commit-records-v2
Oct 20, 2026
Merged

cristianoc merged 1 commit into
masterfrom
cristianoc/commit-records-v2

Conversation

@cristianoc

@cristianoc cristianoc commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

A commit states its change as the semantic objects it changes: the parse tree, the typed tree, a typing rule, the match compiler's clause matrix, Lambda, the generated JavaScript, a diagnostic, a runtime representation. Each object is shown Before and After. This PR has two halves.

Writing: the skill. .claude/skills/records/SKILL.md tells an author, usually an agent, how to write a commit message this way:

  • an object block names the object that changes (AST modified (raw extensions):);
  • an optional example program follows;
  • the object's Before and After come in its own form: a tree as the tree, a rule as an inference rule, a matrix as its rows, output as code or text.

A small commit is a subject and a line or two, with no blocks.

Drawing: the page. The Semantic Diff page draws each commit's message:

  • trees with connectors, highlighted code, inference rules, matrices, mappings, diagnostic text;
  • Before and After side by side on aligned rows, with the changed parts shaded.

scripts/recordRender.js parses and draws a message, scripts/commitRecords.js writes the page for a range of commits, and make semantic-diff renders master..HEAD.

A generic mechanism, instantiated for ReScript. The mechanism has three parameters: the notion of semantic object, the format each object is written in, and the way each is rendered. This PR fixes them for the compiler: objects are trees, rules, matrices, Lambda, output and diagnostics, written as the blocks above and drawn as the drawings above. Another project instantiates the same mechanism with its own objects, formats and renderings.

Examples. #8909 re-applies six merged changes, each with its message written by the skill. Its Semantic Diff link shows a parse tree, a clause matrix, Lambda, generated JavaScript, formatter output and a diagnostic.

Related

Test plan

  • node --test scripts/commitRecords.test.js: 23 passing.
  • npx biome check scripts/, npx tsc and scripts/format_check.sh: clean.
  • The examples PR's page, rendered by this PR's renderer.

🤖 Generated with Claude Code

@pkg-pr-new

pkg-pr-new Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@8908

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@8908

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@8908

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@8908

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@8908

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@8908

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@8908

commit: 963328d

@github-actions

Copy link
Copy Markdown

@cristianoc
cristianoc added this pull request to stack #8911 October 19, 2026 05:03
@cristianoc
cristianoc requested a review from cknitt October 19, 2026 05:06
@cknitt

cknitt commented Oct 19, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T08:25:14.991401Z 56c69ba Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56c69ba029

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/recordRender.js Outdated
Comment on lines +377 to +378
const lhs = [...g.slice(0, at), ...(left.trim() ? [g[at]] : [])];
const rhs = [...(right.trim() ? [g[at]] : []), ...body];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Shade only the changed side of inline rewrites

When two rewrite forms have an identical left-hand side but a changed right-hand side, lineDiff marks the entire source line and including g[at] in lhs causes that unchanged left-hand side to receive del/add shading in both versions. For example, the tested (+ x 0) ⇝ (+ x 0) to (+ x 0) ⇝ x rewrite visually reports (+ x 0) itself as changed, making the semantic diff inaccurate; compare and shade the split left and right fragments independently.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: the terms of a rewrite pair are compared position by position, so (+ x 0) ⇝ (+ x 0) against (+ x 0) ⇝ x shades only the right side. Covered by "a pair of rewrites shades each side where it changes".

@cknitt cknitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the forms and drawings read well, and the escaping holds up (I tried HTML in the subject, author, captions, trees, matrices and mappings). I ran recordRender.js on some message shapes outside the tests and found a few parser problems. The first three seem worth fixing before merge.

1. Trailers other than Signed-off-by / Co-Authored-By end the trailer block early (trailerStart, TRAILER)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_x
Signed-off-by: A <a@b>

The scan stops at Claude-Session. The first two lines render as a prose paragraph on the card, and the co-author list comes out empty. Reviewed-by:, Fixes: and Closes: hit the same problem. Could the last paragraph count as trailers when every line is Key: value (or a continuation line), or could it go through git interpret-trailers --parse?

2. Content on the heading line produces a false "Before and After are equal" (forms)

JS modified (output):
Before (js): return x + 0 | 0;
After (js): return x;

Both texts become captions and both bodies are empty, so the pair compares equal. The page then says "Before and After are equal" for a change. Tab-indented bodies end up the same way, because only spaces count as indentation. Two empty bodies shouldn't produce the "equal" claim, or the captions should be part of the comparison.

3. A paragraph after Why: … / Breaking: … merges into that section (parseMessage, the blank-line branch)

Why: the problem.

A separate paragraph.

Breaking: old flag removed.

Another unrelated paragraph.

A blank line resets only unnamed sections. So "A separate paragraph." ends up in the Why box, and "Another unrelated paragraph." ends up in the Breaking banner. This only happens when the heading has text on the same line.

4. Blocks start at 2 spaces of indentation, but the skill says 4 (/^ {2,}\S/)

A wrapped bullet with a 2-space continuation line (the style of changes/ fragments) splits into a paragraph plus a detached block:

- first bullet that wraps
  onto a second line

5. Indented code under a plain paragraph renders as prose

The new output for:

    let f = x => x + 1
    let g = y => y

This renders as <p>let f = x => x + 1 let g = y => y</p>. prose() dedents first, so a uniformly indented block counts as text.

Smaller

  • A prose line starting with Example: (e.g. "Example: when the argument is constant, …") renders as a ReScript code block, even outside an object block.
  • In rules, a change anywhere shades the whole figure. The skill says "the changed parts shaded".
  • In code, a ⇝ b ⇝ c drops everything after the second ⇝.
  • Codex's open finding on rewrite shading (recordRender.js:378) still applies. I haven't repeated it here.

Most of these would make good additions to commitRecords.test.js.

@cristianoc
cristianoc force-pushed the cristianoc/commit-records-v2 branch from 56c69ba to 0308018 Compare October 19, 2026 14:40
@cristianoc

Copy link
Copy Markdown
Collaborator Author

Thanks, all fixed in the amended commit. Each case is now a regression test, and each of those tests fails on the previous renderer.

  1. Trailers: the message's last run of Key: value lines (with continuation lines) is its trailers, whatever the key: Claude-Session, Reviewed-by, Fixes and so on. Trailers are not drawn, and co-authors come from Co-Authored-By anywhere in that run. Why, Review, Breaking and Example directly above the sign-off still draw as sections.
  2. One-line forms: text on a Before/After heading line is the version itself when no block follows, so Before (js): return x + 0 | 0; and After (js): return x; draw as a change. With a block under the heading, the text is its caption. "Before and After are equal" needs equal, non-empty bodies. Tabs count as 4 columns.
  3. Sections: a blank line ends a named section whose text sits on its heading line, so the next paragraph is prose again.
  4. Indentation: a code block starts at 4 spaces, and a 2-space continuation stays in its paragraph or bullet.
  5. Indented code: a block indented under a plain paragraph is drawn as ReScript code.
  6. Smaller:
    • Example: heads a program only when it stands alone on its line.
    • Typing rules shade only the changed premise, conclusion, name or formula.
    • a ⇝ b ⇝ c draws every term.
    • Following the Codex finding, the terms of a rewrite pair are compared position by position, so only the side that changes is shaded.

The skill states the trailer, indentation, one-line form and Example: rules. The #8909 examples render byte-identical with the new parser.

@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.03%. Comparing base (58da277) to head (963328d).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #8908   +/-   ##
=======================================
  Coverage   81.03%   81.03%           
=======================================
  Files         455      455           
  Lines       63640    63640           
=======================================
  Hits        51573    51573           
  Misses      12067    12067           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Why:
    Reviewers reconstruct a change's effect on compiler objects (trees,
    Lambda, JavaScript, diagnostics, typing rules) from its code diff. A
    record states that effect in each object's own form, Before and After,
    and the review page draws it, so a reviewer reads the change at the
    level of the object it changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
@cristianoc cristianoc changed the title Draw each record's forms Before and After on the Semantic Diff page Semantic diff: write and draw changes as semantic objects Oct 20, 2026
@cristianoc
cristianoc removed this pull request from stack #8911 October 20, 2026 04:44
@cristianoc
cristianoc force-pushed the cristianoc/commit-records-v2 branch from 0308018 to 963328d Compare October 20, 2026 04:45
@github-actions

Copy link
Copy Markdown

@cristianoc
cristianoc enabled auto-merge (squash) October 20, 2026 04:48
@cristianoc
cristianoc merged commit f79ec1f into master Oct 20, 2026
29 checks passed
@cristianoc
cristianoc deleted the cristianoc/commit-records-v2 branch October 20, 2026 04:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants