From 8246555b216b4b6815e400b469c971786d42a873 Mon Sep 17 00:00:00 2001 From: Christopher Bays Date: Sun, 14 Jun 2026 17:14:35 -0400 Subject: [PATCH 1/2] =?UTF-8?q?feat(render):=20PR=20review=20comments=20in?= =?UTF-8?q?=20the=20report=20=E2=80=94=20inline=20threads=20+=20counts=20+?= =?UTF-8?q?=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidates #419 + #420 + #421 + #422 (epic #353): the ingested GitHub PR review threads (#395) are anchored onto the rendered diff (the shipped #418 anchoring — re-used verbatim, never re-anchored) and grouped per model, then rendered three ways — gated behind the new `pr-comments` experiment + the `--pr-diff` arm. - #419 inline diff-line comment threads: each anchored thread renders at its Model-SQL diff line (author · body · replies · resolved/open state, GitHub-style). Resolved threads collapse; outdated threads are labeled and surfaced in a per-model tail (never mis-anchored, never dropped). - #420 top-of-report comment-count button: shows the report-wide total and navigates (selects the model via __cuteSelectNode's selectModelByName + scrolls to the anchored diff line). In-page JS only — zero fetch. - #421 per-model count tooltip: the focusable-bubble pattern (.expect-tooltip + CSS bubble on :hover AND :focus, aria-label for AT) — never a native title. - #422 fixture + BDD + dogfood: synthetic --pr-comments review-threads fixture drives the committed comments-showcase golden (visible inline threads + counts); 5 BDD scenarios; headless test asserting inline render, navigation, and tooltip-on-focus; zero-egress gate covers the example. Domain: new pure `pr_comment_render` module (CommentsView / ModelCommentBucket / group_comment_threads) — std+serde only, re-uses anchor_comment_thread. Adapter: additive Option on ReportPayload (JSON-rendered, the pr_dag precedent). CLI: --pr-comments @file value-parser (the gh-fetch live path resolves owner/repo/number from --pr-url/--pr-number) + gather_pr_comments. Gating: byte-identity goldens when the experiment is OFF / no PR context / no comments (the always-present static containers stay empty). All report goldens regenerated for the JS/CSS bundle shift; explore goldens untouched. Closes #419 Closes #420 Closes #421 Closes #422 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 37 +- examples/comments-showcase-report.html | 11700 ++++++++++++++++ examples/diff-showcase-report.html | 313 + examples/jaffle-shop-report.html | 313 + examples/macro-heavy-report.html | 313 + examples/playground-report.html | 313 + examples/prdiff-minidag-report.html | 313 + examples/seed-showcase-report.html | 313 + features/pr_comments.feature | 69 + lefthook.yml | 3 +- src/adapters/render.rs | 70 +- src/cli/args.rs | 27 + src/cli/mod.rs | 127 +- src/cli/pr_comments.rs | 113 + src/cli/review.rs | 7 + src/domain/experimental.rs | 31 + src/domain/mod.rs | 4 + src/domain/pr_comment_render.rs | 684 + templates/interaction.js | 248 + templates/report.css | 60 + templates/report.html | 24 + tests/common/mod.rs | 5 + tests/fixture_parse.rs | 9 +- tests/fixtures/MANIFEST.toml | 26 + .../comments-showcase-pr-comments.json | 85 + tests/headless_toggle.rs | 197 +- ...lden_report__rendered_report_skeleton.snap | 6 +- ...egration__rendered_chrome_jaffle_shop.snap | 313 + tests/steps/mod.rs | 1 + tests/steps/pr_comments.rs | 223 + 30 files changed, 15923 insertions(+), 24 deletions(-) create mode 100644 examples/comments-showcase-report.html create mode 100644 features/pr_comments.feature create mode 100644 src/cli/pr_comments.rs create mode 100644 src/domain/pr_comment_render.rs create mode 100644 tests/fixtures/comments-showcase-pr-comments.json create mode 100644 tests/steps/pr_comments.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cb63f7a..7dae5e71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,6 +343,7 @@ jobs: # - 28 after agent_skill (cute-dbt#304) # - 29 after macro_perspective (cute-dbt#265) # - 30 after explore_macro_view (cute-dbt#345) + # - 31 after pr_comments (cute-dbt#419) # The README is not a feature. The job name deliberately carries no # number (cute-dbt#68 — the old `==6` display label went stale across # several bumps): the enforced count lives ONLY in the `expected=` @@ -360,7 +361,7 @@ jobs: - name: features/*.feature count matches expected run: | set -euo pipefail - expected=30 + expected=31 count=$(find features -maxdepth 1 -name '*.feature' | wc -l | tr -d ' ') echo "feature-count: expected ${expected}, found ${count}" if [ "$count" != "$expected" ]; then @@ -710,6 +711,24 @@ jobs: output: examples/prdiff-minidag-report.html project_root: tests/fixtures/prdiff-minidag-source experimental: "1" + # cute-dbt#419–#422 (epic #353) — the GOLDEN PR review-comments + # showcase. REUSES the prdiff-minidag manifest + patch + source (so + # no manifest regen — the threads anchor onto its real diff lines) + # and adds a SYNTHETIC `gh api graphql` review-threads payload via + # --pr-comments. The `pr-comments` experiment is on, so the committed + # golden exercises the gated surface: an inline OPEN thread on + # stg_orders line 2, a RESOLVED thread + an OUTDATED tail on + # fct_orders, the per-model count tooltip, and the top-of-report + # count button (3 comments total). No baseline — --pr-diff only. + - name: comments-showcase + mode: report + current: tests/fixtures/prdiff-minidag-current.json + baseline: "" + pr_diff: tests/fixtures/prdiff-minidag-pr-diff.patch + output: examples/comments-showcase-report.html + project_root: tests/fixtures/prdiff-minidag-source + experimental: "1" + pr_comments: tests/fixtures/comments-showcase-pr-comments.json # cute-dbt#100 — the GOLDEN explore pages: the full synthetic # playground manifest rendered to the two-file --out-dir shape # (examples/explore/dag.html + tests.html), each independently @@ -790,6 +809,9 @@ jobs: # pins a far-future date so the golden is wall-clock-independent. FINDINGS_OUT: ${{ matrix.findings_out }} GENERATED_AT: ${{ matrix.generated_at }} + # cute-dbt#419: optional synthetic PR review-comments payload (the + # comments-showcase row); empty = no --pr-comments arg. + PR_COMMENTS: ${{ matrix.pr_comments }} run: | set -euo pipefail mkdir -p target/example-check @@ -889,16 +911,25 @@ jobs: findings_args=(--findings-out "$rendered_findings" --generated-at "$GENERATED_AT") findings_hint=" --findings-out $FINDINGS_OUT --generated-at $GENERATED_AT" fi + # cute-dbt#419: the optional --pr-comments arg (the comments-showcase + # row's synthetic review-threads payload). Built only when set. + pr_comments_args=() + pr_comments_hint="" + if [ -n "$PR_COMMENTS" ]; then + pr_comments_args=(--pr-comments "@$PR_COMMENTS") + pr_comments_hint=" --pr-comments @$PR_COMMENTS" + fi cargo run --quiet --locked --bin cute-dbt -- report \ --manifest "$CURRENT_MANIFEST" \ "${scope_args[@]}" \ "${project_root_args[@]}" \ "${config_args[@]}" \ "${findings_args[@]}" \ + "${pr_comments_args[@]}" \ --out "$rendered" if ! diff -q "$rendered" "$OUTPUT_PATH" >/dev/null; then echo "::error::example-report-up-to-date: $OUTPUT_PATH does not match the renderer output ($EXAMPLE_NAME). Regenerate with:" - echo " ${regen_env}cargo run --bin cute-dbt -- report --manifest $CURRENT_MANIFEST$scope_hint$regen_hint$config_hint$findings_hint --out $OUTPUT_PATH" + echo " ${regen_env}cargo run --bin cute-dbt -- report --manifest $CURRENT_MANIFEST$scope_hint$regen_hint$config_hint$findings_hint$pr_comments_hint --out $OUTPUT_PATH" echo "Sizes:" ls -l "$rendered" "$OUTPUT_PATH" exit 1 @@ -910,7 +941,7 @@ jobs: if [ -n "$FINDINGS_OUT" ]; then if ! diff -q "$rendered_findings" "$FINDINGS_OUT" >/dev/null; then echo "::error::example-report-up-to-date: $FINDINGS_OUT does not match the renderer output ($EXAMPLE_NAME findings envelope). Regenerate with:" - echo " ${regen_env}cargo run --bin cute-dbt -- report --manifest $CURRENT_MANIFEST$scope_hint$regen_hint$config_hint$findings_hint --out $OUTPUT_PATH" + echo " ${regen_env}cargo run --bin cute-dbt -- report --manifest $CURRENT_MANIFEST$scope_hint$regen_hint$config_hint$findings_hint$pr_comments_hint --out $OUTPUT_PATH" echo "Sizes:" ls -l "$rendered_findings" "$FINDINGS_OUT" exit 1 diff --git a/examples/comments-showcase-report.html b/examples/comments-showcase-report.html new file mode 100644 index 00000000..080fff93 --- /dev/null +++ b/examples/comments-showcase-report.html @@ -0,0 +1,11700 @@ + + + + + +cute-dbt report + + + + + + +
+

cute-dbt report

+
+ +
+

+ Showing 2 unit tests in scope — from PR file diff. +

+ + + + + + +
+ + +
+ + +
+
+ +
+ +
+
+

PR scope lineage experimental

+ +
+

2 modified models · 1 connector model — modified emphasized, connectors quiet, context dimmed, deleted as ghosts. Click a node to select it below.

+
+ +
+ + +
+
+ + + +
+
+ + + + + +
+ + +
+
+

Coverage checks

+

Static checks over the manifest and compiled SQL — tiers are labeled, never averaged.

+
+
+
+ +
+
+ Model SQLraw, with Jinja + +
+
+
+
+
+ + +
+
+ Model YAMLauthored schema entry +
+
+
+
+
+ +
+
+

CTE dependency DAG

+ + +
+ + +
+
+
+ + +
+
+ node role + +
+
+ edge type + +
+
+
+ +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+ +
+ +
+ +
+

Given

+
+
+
+ +
+
+

Expected

+ 0 rows +
+
+
+ +
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/diff-showcase-report.html b/examples/diff-showcase-report.html index ca7485ac..7abbeed2 100644 --- a/examples/diff-showcase-report.html +++ b/examples/diff-showcase-report.html @@ -2256,6 +2256,66 @@ color: var(--text-muted); overflow-wrap: anywhere; margin: 0.5rem 0; } +/* PR review comments (cute-dbt#419 / #420 / #421 / #422, epic #353). + The whole surface is JS-rendered from DATA.pr_comments; these rules + dress the inline threads, the top count button, and the per-model count + chip. No `overflow:hidden` ancestor clips the focusable-bubble tooltip + (the .model-comment-count-chip reuses the shared .expect-tooltip family, + whose bubble is position:absolute over a position:relative trigger). */ +.pr-comments-count { + display: inline-flex; align-items: center; gap: 0.4rem; + margin: 0.6rem 0 0; padding: 0.35rem 0.7rem; + border: 1px solid var(--border, #ccc); border-radius: var(--radius-sm, 6px); + background: var(--surface-2, rgba(0,0,0,0.03)); color: var(--text, inherit); + font: inherit; font-size: 0.92em; cursor: pointer; +} +.pr-comments-count:hover { background: var(--surface-3, rgba(0,0,0,0.06)); } +.pr-comments-count:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.pr-comments-count-label { font-weight: 600; } +.pr-comments-count[hidden] { display: none; } + +.model-comments-row { margin: 0.3rem 0; } +.model-comments-row[hidden] { display: none; } +.model-comment-count-chip { + border-bottom: none; padding: 0.15rem 0.5rem; + border: 1px solid var(--border, #ccc); border-radius: 999px; + background: var(--surface-2, rgba(0,0,0,0.03)); cursor: help; +} +.model-comment-bubble { text-align: left; } +.model-comment-bubble-row { display: block; white-space: nowrap; padding: 0.05rem 0; } + +/* Inline thread cards, injected after the anchored Model-SQL diff line. */ +.pr-comment-thread { + display: block; margin: 0.3rem 0 0.5rem; padding: 0.5rem 0.7rem; + border: 1px solid var(--border, #ccc); + border-left: 3px solid var(--accent); + border-radius: var(--radius-sm, 6px); + background: var(--surface-1, rgba(0,0,0,0.02)); + font-size: 0.9em; white-space: normal; +} +.pr-comment-resolved { border-left-color: var(--ok, #2e7d32); opacity: 0.85; } +.pr-comment-outdated { border-left-color: var(--warn, #b45c0c); } +.pr-comment-thread-head { + display: flex; align-items: center; gap: 0.4rem; + margin-bottom: 0.35rem; color: var(--text-muted); +} +.pr-comment-anchor { font-weight: 600; } +.pr-comment-badge { + font-size: 0.78em; padding: 0.05rem 0.4rem; border-radius: 999px; + border: 1px solid currentColor; +} +.pr-comment-badge-resolved { color: var(--ok, #2e7d32); } +.pr-comment-badge-outdated { color: var(--warn, #b45c0c); } +.pr-comment-collapse > summary { cursor: pointer; color: var(--text-muted); } +.pr-comment { margin: 0.25rem 0; } +.pr-comment-author { font-weight: 600; color: var(--accent); } +.pr-comment-text { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 0.1rem; } +.pr-comment-tail { + margin: 0.6rem 0; padding: 0.5rem 0.7rem; + border: 1px dashed var(--border, #ccc); border-radius: var(--radius-sm, 6px); +} +.pr-comment-tail-label { margin: 0 0 0.4rem; color: var(--text-muted); font-style: italic; } + /* end of cute-dbt report chassis v1 (cute-dbt#177) */ @@ -2273,6 +2333,9 @@

cute-dbt report

+ + + @@ -2525,6 +2588,8 @@

PR scope lineage