Skip to content

#111 — feat: inline SQL diff for changed models in PR-review report - #113

Merged
cmbays merged 5 commits into
mainfrom
adapters-111-model-sql-diff
May 31, 2026
Merged

cmbays merged 5 commits into
mainfrom
adapters-111-model-sql-diff

Conversation

@cmbays

@cmbays cmbays commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an inline SQL diff for changed models in --pr-diff (report) mode, mirroring the inline YAML diff #96 shipped for unit tests. When a model's .sql source changed in the PR, its Model SQL section gains a Raw ↔ Diff toggle showing context / removed / added lines with intra-line emphasis. Diff-sourced (no base manifest): the current SQL comes from the manifest's raw_code, the deltas from the PR diff hunks.

Closes #111.

What's inside

⚠️ Also fixes a latent panic in the shipped #96 YAML drawer

The reconstruction is contracted on git diff --unified=0. A default git diff (3 lines of context) yields hunks where new_len > +-body-count — which the new indexing path and the already-shipped #96 YAML drawer (shared reconstruct_one) would index out of bounds → panic. Both paths now detect a non---unified=0 hunk (hunk_is_unified_zero) and degrade the whole block to the plain view (consistent with the existing stale-diff fallback) — never a panic, never a context-as-added mislabel. New regression tests drive a real default-context git diff end-to-end through the binary (exit 0 + report written) and exercise both the SQL and YAML reconstruction paths.

Review

Built TDD; ran the /atdd gates (CRAP, mutation, architecture); then an independent multi-agent review before opening this PR (code-review, silent-failure, type-design, test-coverage). That review caught the panic above (a BLOCKER) plus several MINOR/NIT items — all addressed in this PR before it opened. The fix diff was read line-by-line.

Versioning

No BREAKING CHANGE — the BlockDiff rename is internal (the lib is internal-only in v0.x) and the SQL diff + whitespace handling are additive ⇒ a v0.x patch.

Gates

nextest 541 · bdd 74 scenarios / 477 steps (feature-count stays 10) · headless toggle + zero-egress 7 (real Chromium) · coverage 98.79% lines (--fail-under-lines 85) · crap4rs PASS · cargo mutants 0-missed on the changed reconstruction code · clippy --all-targets --locked 0 · rustdoc -D warnings --document-private-items 0 · cargo deny ok · mdbook build ok · examples + manifest-digest snapshot byte-stable.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added inline SQL diff rendering for model changes with a Raw ↔ Diff toggle in the Model SQL section.
    • Whitespace-only edits are excluded from inline diffs and rendered as context.
  • Documentation

    • Documented Model SQL section features, diff toggle behavior, and whitespace handling in diffs.
  • Tests

    • Expanded test coverage for model SQL diffs and PR-diff scoping scenarios.

cmbays and others added 3 commits May 31, 2026 13:17
Apply the #96 line-diff substrate to a changed model's raw SQL
(raw_code), rendered in the Model SQL section with a Raw↔Diff toggle.
report-mode, diff-sourced (--pr-diff), no base manifest.

- domain/pr_diff: rename YamlBlockDiff -> BlockDiff (content-agnostic;
  JSON field `yaml_diff` kept so #96 wire/JS/snapshots don't move).
  Widen block_aligns_with_hunks + reconstruct_one to (raw, start, end).
  Add reconstruct_model_sql_diffs over manifest raw_code (whole-file
  span; strip exactly one trailing \n for git's line frame — handles
  the dbt-core vs dbt-fusion trailing-newline divergence). N7b drift
  guard reused; stale/untouched/whitespace-only -> plain SQL view.
- Whitespace ignored as standard, per line-pair (ws_equal,
  git --ignore-all-space semantics) across both the YAML (#96) and SQL
  (#111) diffs. N7b stays whitespace-exact.
- render: ModelPayload.sql_diff (skip_serializing_if, mirrors
  TestPayload.yaml_diff); threaded through build_payload/render_report.
  PrDiff-arm-gated in cli; Baseline => empty map (digest snapshot
  unmoved).
- template: renderYamlDiff -> renderBlockDiff(diff, highlightFn) with
  highlightSql/highlightYaml; highlight-bypass preserved. Model SQL
  Raw↔Diff toggle (default Diff when sql_diff present).
- BDD: 4 scenarios appended to pr_diff_scoping.feature (count stays
  10); headless SQL-diff toggle tests; book docs (raw_code source,
  whitespace standard + caveat, engine trailing-newline note).

Closes #111

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n set)

Close the focused-set DoD on src/domain/pr_diff.rs:
- intra_line_span_suffix_stops_at_the_prefix_boundary: asymmetric
  shared-prefix+suffix cases that kill the 3 suffix-bound mutants
  (`< -> <=`, `a.len()-prefix -> +`, `b.len()-prefix -> +`). These
  were pre-existing #96 survivors (intra_line_span is byte-identical
  to #96); the function is the substrate #111 generalizes, so it's
  hardened in-PR. The remaining 2 `+= -> *=` survivors are infinite
  loops, classified equivalent inline.
- block_misaligns_when_hunk_claims_a_line_the_block_lacks: covers the
  out-of-range guard the (raw,start,end) widening exposed, restoring
  block_aligns_with_hunks to 100% line coverage (CRAP 15.02 -> 15.00,
  unambiguously within the strict-15 gate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…review follow-ups

BLOCKER (review 2026-05-31 — both reviewers reproduced an index-OOB
panic): `fold_hunk_edits` looped `0..h.new_len` and indexed
`added_lines[k]`/`removed_lines[k]` via `pair_is_ws_only`. `new_len`
(from the `@@` range) is independent of `added_lines.len()` (counted
`+` bodies), so a DEFAULT `git diff` (3 context lines, parser drops
them) yields `new_len > added_lines.len()` and panics — regressing the
shared #96 YAML drawer too (reconstruct_one is shared).

Fix (both requirements):
- No panic: `hunk_is_unified_zero(h)` = `new_len == added_lines.len()`
  (always true under --unified=0). `reconstruct_one` checks ALL
  touching hunks are unified-zero; if any isn't, it degrades the whole
  block to the plain view (all-Context -> has_real_change()==false ->
  caller shows plain text), consistent with the stale-diff degrade.
- No silent mislabel: degrade is whole-block, so uncovered new-side
  lines are never labeled Added.
- RED regression: a default-context git diff STRING end-to-end through
  parse_diff -> reconstruct_* (tests/changed_files_provider.rs) +
  domain-level context-bearing-hunk tests (SQL + YAML paths). The #96
  malformed-empty-body test now asserts the consistent degrade.

Review MINOR/NIT:
- BlockSpan<'a> { raw, start, end } borrowed arg helper threaded through
  block_aligns_with_hunks / reconstruct_one (kills the adjacent-usize
  swap hazard; not owned/serialized POD).
- BDD `model_carries_sql_diff` tightened: asserts removed-immediately-
  before-added ORDER + line text + surrounding Context.
- Renamed test -> block_diff_serializes_to_the_exact_renderblockdiff_contract.
- Added model_sql_diff empty-raw_code (`Some("")`) degrade test.
- Hardened is_clean_1to1 (`&&`-mutant kill) + intra_line_span
  suffix-bound mutants + block_aligns out-of-range coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cmbays, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 45 minutes and 57 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d769940b-ba29-4a81-af3b-75e9708ef3a9

📥 Commits

Reviewing files that changed from the base of the PR and between ea43ec9 and 681268d.

📒 Files selected for processing (1)
  • src/domain/pr_diff.rs
📝 Walkthrough

Walkthrough

This PR implements inline SQL diff rendering for the PR-review report, mirroring the unit-test YAML diff from #96. It generalizes block-diff reconstruction to a neutral BlockDiff POD, adds model-SQL diff reconstruction from PR diff hunks, threads sql_diff through the render payload, and extends the Model SQL section with a Diff↔Raw toggle in the interactive UI.

Changes

Inline SQL diff rendering for PR-review

Layer / File(s) Summary
Block diff generalization and model SQL reconstruction
src/domain/pr_diff.rs
Introduces BlockSpan (borrowed block representation) and BlockDiff (neutral diff POD replacing YamlBlockDiff). Adds context_bearing_hunk_count() to detect non-–unified=0 hunks, refactors block_aligns_with_hunks to work with BlockSpan, adds ws_equal whitespace-insensitive comparator, and generalizes reconstruct_one to suppress whitespace-only diffs. Adds new reconstruct_model_sql_diffs function for SQL reconstruction keyed by model full node id.
Renderer payload and signature updates
src/adapters/render.rs
Adds sql_diff: Option<BlockDiff> field to ModelPayload (skipped if absent), changes TestPayload.yaml_diff to use BlockDiff, and updates build_payload/render_report to accept both yaml_diffs and sql_diffs maps. All call sites and tests updated with new HashMap arguments. New tests verify sql_diff inclusion/omission behavior.
CLI SQL diff reconstruction and context-bearing hunk warning
src/cli/mod.rs, src/domain/mod.rs
Builds sql_diffs map using reconstruct_model_sql_diffs in PrDiff mode (empty map in baseline). Adds warn_if_not_unified_zero helper to emit stderr guidance when PR diff contains context hunks explaining inline diffs are disabled. Updates render function signature and re-exports reconstruct_model_sql_diffs and ws_equal.
Generic block diff rendering and Model SQL UI
templates/report.html, examples/*.html
Introduces renderBlockDiff(diff, highlightFn) to render both YAML and SQL diffs with context-line highlighting and intra-line emphasis. Refactors renderYamlDiff as an alias. Updates renderModelSql to detect m.sql_diff, render a Diff↔Raw toggle (defaulting to Diff) with Copy always copying raw SQL, and fall back to plain SQL when no diff is present. Exports window.__cuteRenderBlockDiff for headless tests.
Test infrastructure: Cucumber steps and UI integration
tests/steps/world.rs, tests/steps/builders.rs, tests/steps/pr_diff_scoping.rs, tests/headless_toggle.rs, tests/changed_files_provider.rs, tests/render_integration.rs, tests/golden_report.rs
Extends Cucumber World with model_sql_targets field and new ModelSqlTarget/ModelSqlTargetKind types for SQL diff scenario routing. Adds model_node_with_raw_code builder and MODEL_SQL_RAW_CODE constant. Implements emit_model_sql_hunk to synthesize PR diffs from manifest raw_code (handling Edit, Whitespace, Stale cases). Adds Given/Then steps for SQL diff scenarios and render_pr_diff_with_sql_diffs helper. Includes two UI tests for toggle behavior and two CLI integration tests for context-bearing hunk warning.
User-facing documentation
book/src/how-it-works.md, book/src/recipes/github-actions-pr-review.md, features/pr_diff_scoping.feature
Documents "Model SQL section" behavior: Raw↔Diff toggle conditions, inline diff generation from raw_code, cross-engine trailing-newline normalization, and whitespace-only suppression rules. Explains drift-detection and –unified=0 hunk limitations. Adds four BDD scenarios covering changed-SQL diff, no-change-SQL, stale-diff fallback, and whitespace-only suppression.

Sequence Diagram

sequenceDiagram
  participant CLI as cli::execute
  participant Reconstruct as reconstruct_model_sql_diffs
  participant Render as render_report
  participant Payload as ModelPayload
  participant Template as renderModelSql
  CLI->>Reconstruct: raw_code, hunks, scoped models
  Reconstruct->>Reconstruct: hunk-to-BlockSpan alignment check
  Reconstruct->>Reconstruct: whitespace-only filter
  Reconstruct-->>CLI: HashMap~full_id, BlockDiff~
  CLI->>Render: yaml_diffs, sql_diffs
  Render->>Payload: sql_diff: Option~BlockDiff~
  Render-->>CLI: ReportPayload
  Template->>Template: detect m.sql_diff
  Template->>Template: render Diff/Raw toggle
  Template->>Template: renderBlockDiff(sql_diff, highlightSql)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • breezy-bays-labs/cute-dbt#96: This PR reuses the inline diff reconstruction, BlockDiff POD, intra-line span emphasis, and drift-guard machinery that #96 built for unit-test YAML, applying it to model raw_code diffs.
  • breezy-bays-labs/cute-dbt#86: The PR depends on PR #86's PR-diff scoping mechanism that matches changed file paths to Node::original_file_path, which enables model SQL file targeting.
  • breezy-bays-labs/cute-dbt#43: This PR extends the Cucumber test harness and World infrastructure introduced in #43 with new model_sql_targets fields and SQL diff scenario steps.

Poem

🐰 A diff for the SQL, both raw and refined!
Toggle the view—Jinja-blessed, line-aligned.
BlockDiff knows YAML and code just the same,
One drift-guard dance, one intra-line frame.
Report mode now shows what your models redesigned!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding inline SQL diff for changed models in PR-review report, which is the primary feature implemented in this PR.
Linked Issues check ✅ Passed The PR fully implements all acceptance criteria from #111: inline SQL diff with Raw↔Diff toggle, diff-sourced from PrDiff hunks, block-alignment drift-guard, intra-line emphasis reuse, whitespace handling, and proper fallback for non-unified diffs.
Out of Scope Changes check ✅ Passed All changes are scoped to #111: generalizing diff infrastructure (YamlBlockDiff→BlockDiff), adding sql_diff to ModelPayload, threading through rendering, UI toggle implementation, and comprehensive test coverage for model SQL diffs.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch adapters-111-model-sql-diff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

📄 Rendered report preview

All examples regenerated cleanly.

Example Artifact
jaffle-shop-report.html Download
playground-report.html Download

Click Download to fetch the rendered HTML. Each artifact
is a zip containing the single-file report — extract and open
in any browser. Reports are fully self-contained (zero
external resource requests).

Alternative: GitHub CLI
# gh CLI >= 2.63 extracts into ./report-preview-playground/.
gh run download 26724330353 -R breezy-bays-labs/cute-dbt -n report-preview-playground
open report-preview-playground/playground-report.html

Posted by report-preview.yml for 681268d301a7cf0c46f240a6845ed8c252fd775c. Affordance only — never blocks merge.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces an inline SQL diff feature for changed dbt models' raw Jinja source (raw_code), adding a Raw ↔ Diff toggle in the Model SQL section of the report. To support this, the existing inline YAML diff logic was generalized into a content-agnostic BlockDiff structure, and standard whitespace-only change filtering was implemented to ignore re-indentations and trailing whitespace. The review feedback points out an opportunity to simplify the pair_is_ws_only helper function by removing redundant carriage return trimming, as the downstream whitespace comparison already handles these characters.

Comment thread src/domain/pr_diff.rs
cute-dbt#111 follow-up (Christopher-approved, same PR): a context-bearing
diff silently degrades every affected block to the plain view (the
reconstruct_one contract guard). Emit ONE actionable stderr line so a user
who forgot --unified=0 isn't left thinking inline diffs are broken.

- domain (pure, std-only, no I/O): NormalizedDiffIndex::context_bearing_
  hunk_count() counts hunks where new_len != added_lines.len() — exact
  for "not --unified=0" (insert N==N / delete 0==0 / replace N==N always
  hold under -U0). Unit-tested (exact count on a mixed diff; 0 on a clean
  -U0 diff incl. a pure-deletion hunk); 3/3 mutants caught.
- cli (the I/O lives here, not in domain): warn_if_not_unified_zero emits
  once on the PrDiff arm only:
  "cute-dbt: warning: the supplied diff is not `git diff --unified=0`
   (N context-bearing hunk(s)); inline diffs are disabled — showing plain
   views. Re-run the diff with --unified=0 for inline diffs."
- integration (tests/changed_files_provider.rs): the note is PRESENT on a
  default-context diff and ABSENT on a clean --unified=0 diff.

Examples + manifest-digest snapshot byte-stable (stderr-only, no payload
or template change). Feature-count stays 10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/domain/mod.rs (1)

53-56: 💤 Low value

Reduce public API surface: don’t re-export ws_equal (unless required externally)

ws_equal is only defined and used inside src/domain/pr_diff.rs and is merely re-exported from src/domain/mod.rs; no other non-test code references it (the only test mention is in a comment). Consider making it pub(crate) or removing it from the domain re-exports if downstream consumers don’t rely on it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/domain/mod.rs` around lines 53 - 56, The public re-export list in
src/domain/mod.rs currently exposes ws_equal even though it is only used
internally in pr_diff; remove ws_equal from the pub use list (or change its
visibility in src/domain/pr_diff.rs to pub(crate) if internal tests require
access) so it is no longer part of the crate's public API; locate the re-export
line that lists reconstruct_block_diffs, reconstruct_model_sql_diffs,
refine_changed_by_hunks, ws_equal and either delete ws_equal from that list or
adjust the ws_equal symbol in pr_diff to pub(crate) and run tests to ensure
nothing outside the crate depends on it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/domain/mod.rs`:
- Around line 53-56: The public re-export list in src/domain/mod.rs currently
exposes ws_equal even though it is only used internally in pr_diff; remove
ws_equal from the pub use list (or change its visibility in
src/domain/pr_diff.rs to pub(crate) if internal tests require access) so it is
no longer part of the crate's public API; locate the re-export line that lists
reconstruct_block_diffs, reconstruct_model_sql_diffs, refine_changed_by_hunks,
ws_equal and either delete ws_equal from that list or adjust the ws_equal symbol
in pr_diff to pub(crate) and run tests to ensure nothing outside the crate
depends on it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7d23cb4-89e3-48d3-8263-7e99fb09a0d3

📥 Commits

Reviewing files that changed from the base of the PR and between ee78ca4 and ea43ec9.

⛔ Files ignored due to path filters (1)
  • tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap is excluded by !**/*.snap
📒 Files selected for processing (17)
  • book/src/how-it-works.md
  • book/src/recipes/github-actions-pr-review.md
  • examples/jaffle-shop-report.html
  • examples/playground-report.html
  • features/pr_diff_scoping.feature
  • src/adapters/render.rs
  • src/cli/mod.rs
  • src/domain/mod.rs
  • src/domain/pr_diff.rs
  • templates/report.html
  • tests/changed_files_provider.rs
  • tests/golden_report.rs
  • tests/headless_toggle.rs
  • tests/render_integration.rs
  • tests/steps/builders.rs
  • tests/steps/pr_diff_scoping.rs
  • tests/steps/world.rs

…iew)

ws_equal compares split_whitespace token sequences, which already ignore a trailing carriage return, so the trim before it was dead. Gemini review finding on PR #113. Behavior-preserving: 543 tests green, clippy --locked clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

feature: inline SQL diff for changed models in PR-review report (report-mode, diff-sourced)

1 participant