Skip to content

TML-2771: migrate --show — read-only preview of the migration path#735

Merged
wmadden merged 42 commits into
mainfrom
tml-2771-migrate-show-preview-the-path-ordered-migrations-migrate
Jun 7, 2026
Merged

TML-2771: migrate --show — read-only preview of the migration path#735
wmadden merged 42 commits into
mainfrom
tml-2771-migrate-show-preview-the-path-ordered-migrations-migrate

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What & why

Adds migrate --show — a read-only preview that answers "what will migrate do when I run it?" before you advance the live database. The migration system is a contract graph (not a linear stack), so "the next migration" isn't obvious; this gives users a trustworthy sanity check at the moment they act, and unifies the way the graph is drawn with the way refs are typed.

Reshaped from the original migration path --from --to idea after a design discussion — a preview belongs on the migrate verb the user already reaches for, not a separate command to learn. Full reasoning: projects/migration-graph-rendering/decisions.md § migrate --show (D-MS1–D-MS7); spec + plan + review log under projects/migration-graph-rendering/slices/migrate-show-preview/.

Refs: TML-2771.

What it does

$ prisma-next migrate --show --to prod --db $DATABASE_URL

○   a94b7b4   @contract (prod)
│↑  add_posts        ef9de27 → a94b7b4      ← bright green (will run)
○   ef9de27   @db
  ⋯ off-path nodes dimmed ⋯

Will run, in order:
  1. add_posts   (ef9de27 → a94b7b4)
  • Read-only. Reads the live marker, computes the path, renders — and returns before the apply step. Never writes.
  • Faithful. The path is computed by the same seam real migrate uses (graphWalkStrategy), via a shared planMemberPath helper that both executeMigrate and the preview call — so the preview cannot show a path different from what migrate would run.
  • @db / @contract vocabulary. New reference tokens (@db = the live marker, connection-required; @contract = the working contract, offline) usable in --from/--to. The graph + --legend now draw reserved markers the same way you type them (@db/@contract), dropping the old <contract, db> angle-bracket form.

Changes (commit-per-dispatch)

  1. D1 — render reserved markers as @db/@contract in the shared overlay and --legend; regen graph/status/list snapshots.
  2. D2@db/@contract reference-grammar tokens in parseContractRef.
  3. D3migrate --show: read-only path compute (stop before runMigration) + ordered execution list + edge cases (no-path / at-target / multi-space / @db-no-connection).
  4. D4 — graph visualization: chosen path bright-green, off-path dimmed, reusing the existing edgeAnnotationsByHash annotation hook (no parallel renderer).
  5. fix — share the path-compute seam (planMemberPath) so the preview is structurally faithful to apply (review F1).

Scope

In: the migrate --show preview, the @db/@contract tokens, and the render-vocabulary unification (overlay + legend) — which intentionally changes graph/status/list output (the <db>@db relabel + legend).

Out / preserved: migrate's apply behaviour is unchanged (the planMemberPath extraction is a pure refactor of its path-compute portion); no behavioural change to plan/verify.

Verification

pnpm typecheck green; cli suite (1209 tests) + migration-tools (549) green; existing migrate apply tests unchanged. The two load-bearing properties are tested: read-only (a module-level runMigration mock throws on any call), and faithfulness (the test asserts graphWalkStrategy's call arguments on an invariant-bearing fixture — it would fail on the pre-fix input divergence). Reviewed across two rounds; the round-1 faithfulness finding (the preview could diverge from apply on invariant-bearing graphs) was fixed by the shared seam.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Read-only preview: migrate --show (with --from/--to) previews the exact ordered migration plan and exits without applying changes; supports offline refs and live-DB markers and emits JSON or human-formatted output.
  • Style / Rendering

    • Marker display uses @-prefixed tokens; graph/tree renders on-path “will run” indicators, lane/color overrides, and app vs extension space @contract visibility.
  • Tests

    • Expanded coverage for preview behavior, markers, graph rendering, path-highlighting, and error cases.

wmadden and others added 8 commits June 5, 2026 07:04
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…+ legend (TML-2771)

The shared Tier-3 overlay previously drew reserved markers in angle-brackets
(`<contract, db>`). Now draws them with the `@`-sigil — `@contract @db` —
matching the token spelling users will type into `--from`/`--to`. User refs
keep parentheses. The `--legend` example markers and explanatory text are
updated in lockstep: the legend now reads `@contract @db  reserved markers —
also typeable as --from/--to tokens`. All tests updated; `graph`/`status`/
`list` snapshot coverage regenerated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
Extends the contract-reference grammar accepted by --from/--to with two
reserved sigil tokens:

- `@contract` resolves offline to the on-disk working contract hash. Requires
  `ctx.contractHash` in the resolution context; returns `not-found` when absent.
- `@db` signals "live database marker, connection required." Returns a sentinel
  `ContractRef` with `provenance.kind === 'reserved-db'`; callers must check
  the provenance and resolve the actual hash via `readAllMarkers()`.

Unit tests added to `contract-ref.test.ts` covering both tokens and the
offline absence of `contractHash` for `@contract`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
… ordered list (TML-2771)

Adds `migrate --show [--from <ref>] [--to <ref>]`: a preview of the path
`migrate` will take, with no writes of any kind.

Design:
- Default from-state: reads the live DB marker via `readAllMarkers()` (requires
  `--db`). Explicit `--from X` runs offline — no connection needed.
- `@contract`/`@db` tokens from D2 are accepted as `--from` values.
- Path computed by `graphWalkStrategy()` — the exact same seam real `migrate`
  uses. The command returns before `runMigration()` (the write boundary).
- Output: a linear, ordered list of migrations that will execute, plus a
  summary line. `--json` emits the structured `MigrateShowResult`.

Edge cases handled: no path (structured error), already-at-target (empty list
+ "nothing to run"), `@db`/no-connection (structured error).

Tests prove: (a) `runMigration` is never called when `--show` is passed, and
(b) `graphWalkStrategy` is called for path computation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
… (TML-2771)

Extends the renderer's existing `MigrationEdgeAnnotation` / `edgeAnnotationsByHash`
hook (no parallel renderer) with a `pathHighlight` field:

- `'on-path'`: edge rendered with bright-green dirName + `↑ will run` suffix.
- `'off-path'`: edge rendered with spaces for the dirName (unlabelled) and a
  dimmed hash column.

`migrate --show` builds the annotation map from the on-path migration hash set
(from D3's `graphWalkStrategy` output) and calls `renderMigrationGraphTree`
via the existing shared Tier-3 renderer. The graph is output before the ordered
execution list, using the @-vocabulary from D1 for @contract/@db markers.

Snapshot test: DB one migration behind target — on-path edge labelled, off-path
edge unlabelled, ordered list present.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…o migrate (TML-2771)

Extract `planMemberPath` from `executeMigrate` in `control-api/operations/migrate.ts`
and call it from both `executeMigrate` and `executeMigrateShowCommand`, so it is
structurally impossible for the preview's graphWalkStrategy inputs to diverge from
the real apply path.

Before: the show command stripped the live marker's invariants (always passing
`invariants: []` to graphWalkStrategy) and ignored the --to ref's invariants
(always using `headRef.invariants = []` from the contract-derived app head ref).
Both divergences produce a wrong `required` set inside graphWalkStrategy and can
select a different path — or return unsatisfiable when apply would succeed.

After: `planMemberPath` assembles targetHash, targetInvariants (from refInvariants
when a --to ref was resolved), and currentMarker (the full live marker with
invariants) identically for both callers. The empty-graph at-head/never-planned
cases are also aligned: the show command now fails loudly for never-planned members
instead of silently continuing.

Strengthen the faithfulness test to assert the actual graphWalkStrategy call
arguments on an invariant-bearing fixture (--to ref with invariants: ['inv-a']).
The old bug would have called graphWalkStrategy with headRef.invariants = [],
failing the new expect(callArg.member.headRef.invariants).toEqual(['inv-a']) assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric requested a review from a team as a code owner June 5, 2026 08:29
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a read-only migrate --show preview mode that computes the exact ordered migration path without performing writes. It introduces @contract and @db reserved tokens for offline and deferred marker resolution, extracts shared path-planning logic, enhances graph visualization with path highlighting, and updates marker formatting from angle brackets to @-sigil syntax throughout the CLI.

Changes

Migrate --show preview mode

Layer / File(s) Summary
Reserved token parsing and types
packages/1-framework/3-tooling/migration/src/refs/types.ts, packages/1-framework/3-tooling/migration/src/refs/contract-ref.ts, packages/1-framework/3-tooling/migration/test/refs/contract-ref.test.ts
RefResolutionContext gains optional contractHash. ContractRefProvenance adds reserved-contract and reserved-db. parseContractRef recognizes @contract (resolves from context) and @db (reserved placeholder). Tests added for both tokens.
Shared path planning extraction
packages/1-framework/3-tooling/cli/src/control-api/operations/migrate.ts
Adds planMemberPath and MemberPathOutcome to centralize per-member path planning; executeMigrate calls helper and routes ok/at-head/never-planned/unreachable/unsatisfiable outcomes and uses helper fields for invariant-path errors.
Preview command implementation
packages/1-framework/3-tooling/cli/src/commands/migrate.ts, packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts
Adds executeMigrateShowCommand, --show and --from options, exports MigrateShowMigration and MigrateShowResult, resolves --to/--from (live DB vs offline), plans per-space migrations via planMemberPath, returns ordered migrations, and renders optional Tier-3 graph output; CLI wiring branches to preview handler when --show is set. End-to-end tests validate read-only behavior, planning inputs, output, and error cases.
Graph visualization and path highlighting
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts, packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts
MigrationEdgeAnnotation gets `pathHighlight?: 'on-path'
Marker formatting and legend text
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-styler.ts, packages/1-framework/3-tooling/cli/test/*
Markers now render as space-separated @name tokens in plain output and as repeated @-prefixed names in ANSI output; legend wording changed to “reserved markers — also typeable as --from/--to tokens”; tests updated.
Per-space rendering gate
packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts, packages/1-framework/3-tooling/cli/src/commands/migration-status.ts, packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-space-render.ts
Adds isAppSpace plumbing so the floating working-contract node and @contract overlay are suppressed for non-app spaces while preserved for the app space.

🎯 4 (Complex) | ⏱️ ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Config
  participant Aggregate
  participant Planner as planMemberPath
  participant DB as LiveDB
  participant Renderer

  CLI->>Config: load config, contract.json
  CLI->>Aggregate: load on-disk aggregates
  alt from omitted or `@db`
    CLI->>DB: readAllMarkers()
    DB-->>CLI: live markers
  else from explicit ref/hash
    CLI->>Config: parse `--from` (may use contractHash)
  end
  CLI->>Planner: planMemberPath(member, targetHash, refInvariants, liveMarker)
  Planner-->>CLI: MemberPathOutcome (plan / unsatisfiable / at-head)
  CLI->>Renderer: renderMigrationGraphTree(edgeAnnotations, isAppSpace)
  Renderer-->>CLI: graphOutput
  CLI->>stdout: formatted show output (graph + ordered migrations)
Loading

Possibly related PRs

Suggested reviewers

  • SevInf

Poem

🐰 I peek before the hop begins,
With @contract and @db in my tin,
No writes tonight — just a bright green trace,
A list of steps, safe in their place,
Hooray — a rabbitly preview grin.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: adding a read-only preview mode (--show) to the migrate command that displays the migration path without applying changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 tml-2771-migrate-show-preview-the-path-ordered-migrations-migrate

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.

@pkg-pr-new

pkg-pr-new Bot commented Jun 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-author-tools@735

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-runtime@735

@prisma-next/family-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-mongo@735

@prisma-next/sql-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-runtime@735

@prisma-next/family-sql

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-sql@735

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-arktype-json@735

@prisma-next/middleware-cache

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/middleware-cache@735

@prisma-next/mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo@735

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-paradedb@735

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-pgvector@735

@prisma-next/extension-postgis

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-postgis@735

@prisma-next/postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/postgres@735

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-orm-client@735

@prisma-next/sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sqlite@735

@prisma-next/extension-supabase

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-supabase@735

@prisma-next/target-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-mongo@735

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-mongo@735

@prisma-next/driver-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-mongo@735

@prisma-next/contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract@735

@prisma-next/utils

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/utils@735

@prisma-next/config

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/config@735

@prisma-next/errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/errors@735

@prisma-next/framework-components

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/framework-components@735

@prisma-next/operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/operations@735

@prisma-next/ts-render

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ts-render@735

@prisma-next/contract-authoring

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract-authoring@735

@prisma-next/ids

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ids@735

@prisma-next/psl-parser

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-parser@735

@prisma-next/psl-printer

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-printer@735

@prisma-next/cli

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/cli@735

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/cli-telemetry@735

@prisma-next/emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/emitter@735

@prisma-next/migration-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/migration-tools@735

prisma-next

npm i https://pkg.pr.new/prisma/prisma-next@735

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/vite-plugin-contract-emit@735

@prisma-next/mongo-codec

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-codec@735

@prisma-next/mongo-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract@735

@prisma-next/mongo-value

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-value@735

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-psl@735

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-ts@735

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-emitter@735

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-schema-ir@735

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-ast@735

@prisma-next/mongo-orm

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-orm@735

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-builder@735

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-lowering@735

@prisma-next/mongo-wire

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-wire@735

@prisma-next/sql-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract@735

@prisma-next/sql-errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-errors@735

@prisma-next/sql-operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-operations@735

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-schema-ir@735

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-psl@735

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-ts@735

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-emitter@735

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-lane-query-builder@735

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-relational-core@735

@prisma-next/sql-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-builder@735

@prisma-next/target-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-postgres@735

@prisma-next/target-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-sqlite@735

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-postgres@735

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-sqlite@735

@prisma-next/driver-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-postgres@735

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-sqlite@735

commit: bbc5130

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 145.06 KB (0%)
postgres / emit 117.07 KB (0%)
mongo / no-emit 76.09 KB (0%)
mongo / emit 70.95 KB (0%)
cf-worker / no-emit 174.63 KB (0%)
cf-worker / emit 143.48 KB (0%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts (1)

453-497: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the actual edge kind for the will run suffix.

Line 486 hardcodes a forward-only glyph. If a preview path contains a rollback or self edge, the gutter still renders the correct / direction, but the suffix becomes ↑ will run or > will run, which makes the preview inconsistent with the path it is showing. Pass the edge kind, or the already-resolved arrow glyph, into formatEdgeAnnotationSuffix(...) instead of synthesizing a forward arrow.

Suggested fix
-function formatEdgeAnnotationSuffix(
-  migrationHash: string,
+function formatEdgeAnnotationSuffix(
+  edge: ClassifiedEdge,
   opts: RenderMigrationGraphTreeOptions,
   style: MigrationListStyler,
+  palette: MigrationGraphTreeGlyphPalette,
 ): string {
-  const annotation = opts.edgeAnnotationsByHash?.get(migrationHash);
+  const annotation = opts.edgeAnnotationsByHash?.get(edge.migrationHash);
   if (annotation === undefined) {
     return '';
   }
@@
   if (annotation.pathHighlight === 'on-path') {
-    const glyph = opts.glyphMode === 'ascii' ? '>' : '↑';
+    const glyph = arrowForEdgeKind(edge.kind, palette);
     const label = 'will run';
     if (!opts.colorize) {
       segments.push(`${glyph} ${label}`);
@@
-    const annotationSuffix = formatEdgeAnnotationSuffix(edge.migrationHash, opts, style);
+    const annotationSuffix = formatEdgeAnnotationSuffix(edge, opts, style, palette);

Also applies to: 768-768

🤖 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
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts`
around lines 453 - 497, formatEdgeAnnotationSuffix currently hardcodes a
forward/up glyph for the "will run" suffix, causing mismatches when the preview
path edge is a rollback or self-edge; change the function signature of
formatEdgeAnnotationSuffix to accept the resolved edge kind or the
already-resolved arrow glyph (e.g., add a parameter like edgeKind or edgeGlyph),
update all callers to pass the edge's kind/glyph (respecting opts.glyphMode)
instead of letting the function synthesize '>'/'↑', and use that passed-in glyph
when building the "will run" segment so the suffix matches the rendered gutter
arrow.
🧹 Nitpick comments (3)
packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts (1)

3-3: ⚡ Quick win

Use pathe instead of node:path for consistency.

This file imports join from node:path, but other test files in this PR (migration-graph.test.ts, migration-legend-commands.test.ts) use pathe. The guidelines prefer pathe over Node's built-in path module across the codebase, including tests.

📦 Proposed fix
-import { join } from 'node:path';
+import { join } from 'pathe';

Based on learnings, the Prisma-next repository prefers importing and using 'pathe' over the built-in 'node:path' module for path operations across the codebase (including CLI commands and tooling files), and the learning states this applies to "any TypeScript file (including tests, test helpers, and integration tooling)".

🤖 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 `@packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts` at
line 3, Replace the import of join from Node's built-in path with the project's
preferred pathe module: change the import that currently pulls join from
'node:path' to import join from 'pathe' so the test uses pathe like the other
migration tests (migration-graph.test.ts, migration-legend-commands.test.ts) and
update any usages if necessary to match pathe semantics.
packages/1-framework/3-tooling/cli/src/commands/migrate.ts (1)

445-445: 💤 Low value

Prefer ifDefined for conditional spreads.

The ternary-based spread ...(graphOutput !== undefined ? { graphOutput } : {}) can be replaced with ...ifDefined('graphOutput', graphOutput) for consistency with the codebase convention.

♻️ Suggested refactor
   return ok({
     ok: true,
     migrations: orderedMigrations,
     summary,
-    ...(graphOutput !== undefined ? { graphOutput } : {}),
+    ...ifDefined('graphOutput', graphOutput),
   });

Based on learnings: When using the Prisma Next ifDefined helper from prisma-next/utils/defined, prefer it for conditional object spreads instead of inline ternary-based spreads.

🤖 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 `@packages/1-framework/3-tooling/cli/src/commands/migrate.ts` at line 445,
Replace the inline ternary spread ...(graphOutput !== undefined ? { graphOutput
} : {}) with the prisma-next helper spread ...ifDefined('graphOutput',
graphOutput) to match project conventions; ensure the ifDefined function from
'prisma-next/utils/defined' is imported (or add it if missing) and remove the
old ternary expression in the object literal where graphOutput is being spread
(look for the object construction in migrate.ts that currently uses
graphOutput).
packages/1-framework/3-tooling/cli/src/control-api/operations/migrate.ts (1)

169-169: 💤 Low value

Prefer ifDefined for conditional spreads.

The ternary-based spread ...(isAppMember ? { refName } : {}) can be replaced with ...ifDefined('refName', isAppMember ? refName : undefined) for consistency with the codebase convention. This improves readability and aligns with the pattern used elsewhere (e.g., line 274).

♻️ Suggested refactor
     const outcome = planMemberPath({
       member,
       aggregate,
       targetHash: memberTargetHash,
       refInvariants: memberRefInvariants,
       liveMarker,
-      ...(isAppMember ? { refName } : {}),
+      ...ifDefined('refName', isAppMember ? refName : undefined),
     });

and

   const walked = graphWalkStrategy({
     aggregateTargetId: aggregate.targetId,
     member: targetMember,
     currentMarker: liveMarker,
-    ...(isAppMember && refName !== undefined ? { refName } : {}),
+    ...ifDefined('refName', isAppMember ? refName : undefined),
   });

Based on learnings: When using the Prisma Next ifDefined helper from prisma-next/utils/defined, prefer it for conditional object spreads instead of inline ternary-based spreads.

Also applies to: 414-414

🤖 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 `@packages/1-framework/3-tooling/cli/src/control-api/operations/migrate.ts` at
line 169, Replace the ternary-based conditional spread ...(isAppMember ? {
refName } : {}) with the repository's ifDefined helper: import ifDefined from
'prisma-next/utils/defined' if not already present, then use
...ifDefined('refName', isAppMember ? refName : undefined) so the object only
includes refName when defined; apply the same change for the other identical
conditional spread in the file that uses refName.
🤖 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.

Outside diff comments:
In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts`:
- Around line 453-497: formatEdgeAnnotationSuffix currently hardcodes a
forward/up glyph for the "will run" suffix, causing mismatches when the preview
path edge is a rollback or self-edge; change the function signature of
formatEdgeAnnotationSuffix to accept the resolved edge kind or the
already-resolved arrow glyph (e.g., add a parameter like edgeKind or edgeGlyph),
update all callers to pass the edge's kind/glyph (respecting opts.glyphMode)
instead of letting the function synthesize '>'/'↑', and use that passed-in glyph
when building the "will run" segment so the suffix matches the rendered gutter
arrow.

---

Nitpick comments:
In `@packages/1-framework/3-tooling/cli/src/commands/migrate.ts`:
- Line 445: Replace the inline ternary spread ...(graphOutput !== undefined ? {
graphOutput } : {}) with the prisma-next helper spread
...ifDefined('graphOutput', graphOutput) to match project conventions; ensure
the ifDefined function from 'prisma-next/utils/defined' is imported (or add it
if missing) and remove the old ternary expression in the object literal where
graphOutput is being spread (look for the object construction in migrate.ts that
currently uses graphOutput).

In `@packages/1-framework/3-tooling/cli/src/control-api/operations/migrate.ts`:
- Line 169: Replace the ternary-based conditional spread ...(isAppMember ? {
refName } : {}) with the repository's ifDefined helper: import ifDefined from
'prisma-next/utils/defined' if not already present, then use
...ifDefined('refName', isAppMember ? refName : undefined) so the object only
includes refName when defined; apply the same change for the other identical
conditional spread in the file that uses refName.

In `@packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts`:
- Line 3: Replace the import of join from Node's built-in path with the
project's preferred pathe module: change the import that currently pulls join
from 'node:path' to import join from 'pathe' so the test uses pathe like the
other migration tests (migration-graph.test.ts,
migration-legend-commands.test.ts) and update any usages if necessary to match
pathe semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 0ec87024-74c6-4719-8809-3961e19910a3

📥 Commits

Reviewing files that changed from the base of the PR and between fa181e3 and d03a89c.

⛔ Files ignored due to path filters (4)
  • projects/migration-graph-rendering/decisions.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/migrate-show-preview/code-review.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/migrate-show-preview/plan.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/migrate-show-preview/spec.md is excluded by !projects/**
📒 Files selected for processing (12)
  • packages/1-framework/3-tooling/cli/src/commands/migrate.ts
  • packages/1-framework/3-tooling/cli/src/control-api/operations/migrate.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-styler.ts
  • packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-graph.test.ts
  • packages/1-framework/3-tooling/cli/test/commands/migration-legend-commands.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-list-styler.test.ts
  • packages/1-framework/3-tooling/migration/src/refs/contract-ref.ts
  • packages/1-framework/3-tooling/migration/src/refs/types.ts
  • packages/1-framework/3-tooling/migration/test/refs/contract-ref.test.ts

wmadden and others added 4 commits June 5, 2026 12:41
… --show (TML-2771)

Two correctness bugs from the operator visual review of PR #735:

BUG 1: @contract marked the --to target hash instead of the app's working
contract. The renderer received contractHash = targetHash; it now receives
contractHash = the working contract from buildReadAggregate, independent
of --to.

BUG 2: @contract leaked into extension spaces when migrate --show rendered
a multi-space graph. Extension space renders no longer receive contractHash,
so no floating @contract node appears in extension sections. The command
now also renders all spaces (app + extensions), matching migrate's real
multi-space behaviour.

Also wires the spec/decisions doc to record the D-MS3 revised decision.

Tests added:
- @contract marks the working contract, not the --to target
- @contract does not appear in extension spaces (multi-space fixture)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…green (TML-2771)

Visual refinements from the operator review of migrate --show PR #735:

REFINEMENT 3: off-path migration rows are now fully drawn in uniform dim
grey — name, hash, and lane lines — instead of showing blank spaces where
the name would be. Nothing is omitted from the graph.

REFINEMENT 4: on-path rows now apply bright green to the lane/gutter
cells (direction arrows, branch lines) and the hash column, not just the
migration name. The whole row reads as one colour.

Both changes are confined to formatEdgeAnnotationSuffix and the edge-row
rendering block in renderMigrationGraphTree. No shared graph/status/list
rendering paths are affected — the pathHighlight annotation is only set by
migrate --show; other commands leave it undefined, taking the unchanged
code paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ng (TML-2771)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…e (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Addressed the visual-review feedback (3 commits: 8b6792f6, bfd85a00, b55f9a5a):

Bugs fixed

  • @contract now marks the app working contract (contract.json hash), not the --to target.
  • The floating @contract node renders only in the app space — extension spaces (e.g. pgvector:) no longer show it.

Visual refinements

  • The whole graph is drawn: off-path nodes/hashes/migrations/lane-lines in uniform dim grey (no longer omitted); on-path node, name, hash and lane lines in bright green.
  • The "will run" list no longer goes through Clack (ui.output, not ui.log) — no gutter — and renders each row in the graph migration-row format + alignment (minus the gutter), in the same green.

Reviewed (opus): all six correct on disk; read-only + faithfulness intact; the shared graph/status/list output is unchanged. One should-fix (colour-wrapper test coverage) — routing is now tested; the literal ANSI hue can't be asserted in-suite because the test env runs NO_COLOR=1, so the green/grey is verified by review + visual confirmation here.

wmadden and others added 2 commits June 5, 2026 13:29
…d renderer (TML-2771)

`@contract` (the app's working/desired contract) is an app-space concept.
`migration graph` and `migration status` both passed the app's
`liveContractHash` to every space's render — including extension spaces
like `pgvector:` — so any extension space whose working-contract was
detached from its graph would show a spurious floating `@contract` node.

Fix: add `isAppSpace?: boolean` (default `true`) to
`RenderMigrationGraphTreeOptions` and gate the `@contract` marker in
`overlayNamesForContract` behind `isAppSpace !== false`. Mirror the gate
in `RenderMigrationGraphSpaceTreeInput` so the space-render layer also
skips passing `contractHash` to `buildMigrationGraphRows` for extension
spaces (suppressing the floating working-contract node, not just its
label).

Update all callers per space:
- `migration-graph.ts`: passes `isAppSpace` derived from `spaceId === aggregate.app.spaceId`
- `migration-status.ts`: same derivation in the per-space loop
- `migrate.ts` (`--show`): drops the caller-side `spaceContractHash` workaround
  in favour of the structural gate (`contractHash` always passed to
  `renderMigrationGraphTree`, `isAppSpace` set per space)

`@db` is not app-gated — it is a per-space marker and must render in
every space.

Add a multi-space renderer test suite (`renderMigrationGraphTree isAppSpace gate`)
covering: app-space shows `@contract`; extension-space suppresses it even
when `contractHash` matches; extension-space produces no floating
working-contract node; `@db` still renders in extension spaces; omitting
`isAppSpace` defaults to app-space behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…s (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Good catch — the @contract-in-extension-spaces issue was indeed present in migration graph and migration status too (both passed the app liveContractHash to every space's render). Fixed structurally in commit 018f9718:

  • Added an isAppSpace gate to the shared Tier-3 renderer: the @contract marker and the floating working-contract node now render only for the app space. @db is left per-space (it legitimately appears in every space).
  • migration graph, migration status, and migrate --show all pass isAppSpace (spaceId === app.spaceId); the earlier migrate --show-only caller workaround was removed so there is one mechanism, not three.
  • 5 new multi-space (app + pgvector) renderer tests assert no @contract / no floating node in the extension section, app section still shows it, @db still renders per-space. Single-space output unchanged (no snapshot diffs).

Reviewed (opus): SATISFIED — marker + floating node both gated, @db intact, all callers correct, no regression.

wmadden and others added 3 commits June 5, 2026 14:05
…2771)

The offline-–from branch in executeMigrateShowCommand was calling
markerBySpace.set(member.spaceId, offlineMarker) for every member,
including extension spaces. This caused planMemberPath to attempt a
graph walk from the app's --from hash in the extension's graph, which
has no such node, producing "No migration path from sha256:76c1bd5
to sha256:... in space 'pgvector'".

Fix: apply the offline marker only to the app member's space ID.
Extension members stay absent from markerBySpace (null / greenfield),
so planMemberPath plans each extension from its own marker → own head,
exactly as executeMigrate does via readAllMarkers().

Adds a multi-space fixture test that passes --from <app-hash> --to
<app-hash> and asserts the extension migration appears in the ordered
list (planned from greenfield) and the suppressed app migration does
not appear (–from scoping is respected).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…on (TML-2771)

The previous implementation applied dim()/greenBright() as a post-hoc
wrapper around the already-rotation-coloured gutter string. This had
two problems:

1. Off-path rows rendered in their rotation colour (e.g. red for column
   5), because dim(red(text)) still displays red — the inner escape
   wins. The expected result is uniform dim grey.

2. Node rows (contract markers, ○/∅) received no path-highlight
   treatment at all — only edge rows had the wrapper applied.

Fix: add an optional laneOverride parameter to renderCellPair (and
renderNodeMarkerPair) that replaces the laneStylerForColumn call at
the source, before any rotation hue is emitted. When laneOverride is
provided (dim for off-path, greenBright for on-path), it wins over
the rotating lane colour for every cell kind in the row.

Also compute contractHighlights before the render loop: a Map from
contract hash to path-highlight derived from edgeAnnotationsByHash
(on-path wins when a contract appears in both). Node rows look up
their contractHash in this map and receive the matching laneOverride,
covering node markers and their adjacent lane segments.

The laneOverride is undefined for every command that does not supply
edgeAnnotationsByHash (graph/status/list), preserving the rotating
coloured-lanes behaviour unchanged for those commands.

Adds regression-guard tests: node rows for on/off-path contracts are
present and contain their hashes; branching-graph test confirms the
on-path edge gets 'will run' suffix and the off-path does not. The
test env runs NO_COLOR=1 so actual ANSI hues cannot be asserted —
the colour routing is confirmed by code inspection and operator
visual review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…-colour override (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Both fixed (commits 8b48ccac + 7aed826e), reviewed SATISFIED:

--from/--to were leaking into extension spaces — they now retarget the app space only. Extension members are previewed exactly as real migrate plans them (own live marker → own head ref), so migrate --show --from <app> --to <app> no longer throws a bogus "no path in pgvector". Multi-space test added.

Off-path branches rendered red (the rotating lane colour) and the on-path highlight only covered one segment. The pathHighlight treatment is now threaded into the per-cell lane-colour decision (it replaces the rotation styler instead of wrapping the already-coloured string), across node-marker rows and every lane segment. So the whole ∅ → 1375f13 path is green end-to-end, and off-path is uniform dim grey — never a rotation colour. graph/status/list are unaffected (the override only fires when path-highlight annotations are present, i.e. only migrate --show).

Colour-hue note: the test env runs NO_COLOR=1, so the literal green/grey is verified by review + your visual confirmation; the structural routing (which rows are on/off-path) is test-covered.

wmadden and others added 4 commits June 5, 2026 14:52
…edule (extensions first) (TML-2771)

The "Will run, in order:" list was built by iterating allMembers which
puts app first, so extension migrations appeared last. The runner
(operations/migrate.ts) applies extensions alphabetically first, then
app. The show command now collects orderedMigrations in the same
canonicalOrderMembers sequence ([...aggregate.extensions, aggregate.app])
so the listed order matches the actual execution order.

Adds a multi-space test asserting the extension migration (pgvector) appears
before the app migrations in the ordered list.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ering (TML-2771)

Five colour bugs fixed in renderMigrationGraphTree:

1. Node hash text was dim+cyan instead of greenBright/dim.
   Now uses rowLaneOverride (forcedGreenBright / forcedDim) instead of
   style.sourceHash when a path-highlight is active.

2. On-path edge from→to hashes had the wrong colour. Wrapping an already-styled
   hashColumn string in an outer greenBright does not work — inner ANSI codes
   (dim+cyan from sourceHash, brightCyan from destHash) override the outer wrapper
   at the terminal level. Fixed by passing the override directly to
   formatEdgeHashColumn, which now accepts an optional hashOverride that replaces
   all sub-stylers (sourceHash, destHash, arrow glyph) uniformly.

3. Off-path destination hash was brightCyan instead of dim. Same root cause as
   above; same fix.

4. Branch connector rows rendered off-path lanes in their rotation colour (e.g.
   magenta for lane 1). Fixed by building a per-column override map from
   edgeAnnotationsByHash + model.edgeColumn and passing it to renderConnectorRow,
   which now applies the override per cell column instead of the rotation colour.

5. All path-highlight functions (pathLaneOverride, dirName, suffix, hashOverride)
   now use forcedGreenBright / forcedDim from createColors({ useColor: true })
   instead of module-level dim / greenBright. The forced variants always emit ANSI
   regardless of NO_COLOR=1, which makes the highlighting reachable in unit tests
   and removes a class of blind-editing errors.

Adds a colour-forced unit test that asserts on-path rows carry \x1b[92m
(greenBright), off-path rows carry \x1b[2m (dim), and no rotation colour
(\x1b[31m red, \x1b[35m magenta) appears on any highlighted row.

Verification: ran the repro topology (∅ forks to on-path chain ∅→76c1bd5→…→f7a8eb5
and off-path ∅→1375f13) through a wip/verify-colors.mts script with FORCE_COLOR=1
and inspected raw ANSI. Before: node hashes dim+cyan, dest hash brightCyan, branch
connector magenta. After: every on-path cell greenBright, every off-path cell dim,
connector branch dim, no rotation colour anywhere.

Graph/status/list snapshots unchanged (path-highlight code is no-op when
edgeAnnotationsByHash is absent).

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ments (TML-2771)

Updates D-MS3 in the migrate-show-preview spec to document the precise colour and
ordering requirements that were repaired in this session: on-path green on every
cell (node markers, contract-hash text, edge from→to hashes, lane segments), off-
path dim grey on every cell including the destination hash, and the "Will run, in
order:" list ordered by the runner's canonical cross-space schedule (extensions
first, then app).

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-highlight colours (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Fixed all five, and — importantly — the colours are now verified against the actual ANSI, not asserted blind (the prior misses were because the test env forces NO_COLOR=1, so the renderer emitted no colour to check; the path-highlight stylers now force colour behind the colorize flag, and a test asserts the escape codes directly).

  • Run-list order (bef98901): now the runner's canonical schedule — extension spaces first, then app — sourced from the same aggregate.extensions/aggregate.app ordering the runner consumes, not a per-space loop. So the preview order is the apply order.
  • Path-highlight colours (e59c0e6d): on-path is bright green on every cell (node markers, node hashes, lane segments on the path's own column, edge from → to hashes, ↑ will run); off-path is uniform dim grey on every cell including the destination hash; no git log --graph rotation colour (red/magenta) anywhere on a highlighted graph. Verified by force-colour rendering the --from @db --to f7a8eb5 topology and inspecting the raw escape codes (on-path \x1b[92m, off-path \x1b[2m, zero rotation codes). NO_COLOR/piped output still emits zero ANSI.

graph/status/list unchanged. Worth a final eyeball on the demo, but the colour mapping is now locked by a test that reads the actual ANSI.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In
`@packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts`:
- Around line 392-395: The current case block treats both 'branch-tee' and
'merge-tee' the same and when seenTee is true it applies
palette.connectorBranchTeeCo, which flips merge glyphs; update the logic in the
switch handling 'branch-tee'/'merge-tee' so that 'merge-tee' uses
palette.connectorMergeTeeCo when seenTee is true (and palette.connectorMergeTee
when seenTee is false), while 'branch-tee' continues to use
palette.connectorBranchTeeCo/ palette.connectorBranchTee; keep the
override(seenTee ? ... : ...) pattern and preserve the seenTee = true
assignment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 723289d5-68c3-41d0-b640-028ffb66ee6a

📥 Commits

Reviewing files that changed from the base of the PR and between 2c440bb and 8f172ab.

⛔ Files ignored due to path filters (2)
  • projects/migration-graph-rendering/slices/migrate-show-preview/code-review.md is excluded by !projects/**
  • projects/migration-graph-rendering/slices/migrate-show-preview/spec.md is excluded by !projects/**
📒 Files selected for processing (4)
  • packages/1-framework/3-tooling/cli/src/commands/migrate.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts
  • packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/1-framework/3-tooling/cli/test/commands/migrate-show.test.ts
  • packages/1-framework/3-tooling/cli/src/commands/migrate.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts

wmadden added 5 commits June 5, 2026 15:54
… green highlight) (TML-2771)

On-path migrations now render in ordinary colours (no forced greenBright override).
Off-path migrations continue to render dim. This removes the forcedGreenBright from
pathLaneOverride, dirName, hashColumnOverride, and the "↑ will run" suffix, replacing
each with either forcedDim (off-path) or no override (on-path). Test updated to assert
no GREEN_BRIGHT on on-path rows.

Co-Authored-By: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…2771)

renderSpaceTreeBlock in migration-list-render.ts was calling renderMigrationGraphSpaceTree
without isAppSpace, so @contract and the floating contract node appeared in every space
including extensions. Thread appSpaceId through RenderMigrationListWithStyleOptions →
renderSpaceTreeBlock → renderMigrationGraphSpaceTree, pass aggregate.app.spaceId at the
call site. Add multi-space test asserting @contract appears only under app:, not pgvector:.

Co-Authored-By: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…TML-2771)

Pre-build all space layouts before rendering so globalMaxEdgeTreePrefixWidth and
globalMaxDirNameWidth can be computed once across every space. Pass both to each
renderMigrationGraphTree call and use globalMaxDirNameWidth as the run-list name
column width, so the name column, from→to hashes, and ops start at the same offset
in every section and the "Will run, in order:" list. Remove the now-unused greenBright
run-list colouring (consistent with FIX A dropping the green highlight). Add a test
asserting the hash column aligns across two spaces with different dirName lengths.

Co-Authored-By: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…lanes dim correctly (TML-2771)

Previously renderMigrationGraphTree applied a single rowLaneOverride to ALL cells in
an edge or node row. This caused off-path branch rows to dim the on-path trunk's
vertical-pass cells (and vice-versa) because the row-level override didn't distinguish
which column each cell belonged to.

Fix: in the gutter-building loop, classify vertical-pass cells by columnHighlights
(the column's dominant edge annotation) and edge-lane cells by their own
cell.migrationHash annotation, rather than using the uniform row-level override.
Connector rows already used per-column overrides via columnLaneOverride and are
unchanged. Node and other structural cells continue to use the row-level (contractHighlights)
classification.

Add test: per-segment case confirms that a vertical-pass at an on-path column in an
off-path row does NOT carry forced-dim, while the off-path name/hash on the same line
still does.

Co-Authored-By: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…umn alignment (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Addressed all four, force-render-verified against the exact topologies:

  • Colour model (61e6ecaa): dropped the bright green — on-path migrations now render in ordinary colours, and the off-path is dimmed grey as the sole differentiator (per your direction). No \x1b[92m remains.
  • Lane/trunk (5e81813a): lane segments classified per-edge; off-path lanes dim, on-path lanes ordinary. Force-rendered --to 6cee614 (trunk now grey) and default --show (on-path lane ordinary, off-path chain dim).
  • migration list @contract gate (b9fe5833): the R4 miss — list now threads isAppSpace like graph/status, so @contract no longer floats in extension spaces. Multi-space test added.
  • Column alignment (802ebbdb): app: and extension sections (and the run-list) now use globally-computed column widths, so the pgvector: section lines up with app:.

Reviewed (opus) with independent force-colour rendering of each case. NO_COLOR/piped → zero ANSI; graph/status/single-space list unchanged.

wmadden added 3 commits June 5, 2026 16:47
…tree-renderer (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
…ff-path dim, rotation suppressed; list shares tree renderer (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
…ur scheme (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Reworked the colour model per your clarification (commit 80dddb1c) — and refactored so future tweaks are one line, not another round:

  • migrate --show now uses a distinct two-style scheme, not an overlay on the branch-rotation. The git log --graph rotating colours are suppressed entirely in this mode.
  • On-path = the neutral single-path style (renders byte-for-byte identical to a single-path section like pgvector — bold name, neutral lanes/arrows, cyan hashes). Off-path = dim grey. Force-render-verified: zero rotation codes (red/magenta/green) on any on-path cell; off-path all dim.
  • One seam: PATH_HIGHLIGHT_STYLES (migration-graph-tree-render.ts:200) — on-path/off-path colours are each a one-line change there. Your follow-up colour tweaks land in that one spot.
  • The run-list renders through the same on-path row renderer as the tree (item 2), so theyre guaranteed consistent.

Reviewed (opus) by independently force-rendering the --to 6cee614 topology and inspecting ANSI; renderer colour tests 83/83.

wmadden and others added 3 commits June 5, 2026 17:41
…idated run message (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
…gnment, consolidated run message (TML-2771)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
… (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden

wmadden commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Three tweaks (commit 2c827e7d):

  • On-path branch is now GREEN — the lane/branch glyphs (│ ├ ╯ ↑ + on-path node markers) render bright green, tracing the path as a continuous green line; off-path stays dim, name/hashes stay neutral. (One-line change in the PATH_HIGHLIGHT_STYLES.onPath seam.)
  • Run-list aligned with the graph — the list is left-padded to the graphs data-column offset and shares the source-hash padding, so every in the whole output (graph rows + list) lines up at the same column.
  • Consolidated messageThe following N migration(s) will run: replaces the separate N migrations will run + Will run, in order: lines.

Force-render-verified (green on-path , dim off-path node, aligned at the same column, single header). Full cli suite green: 1232/1232 (the earlier timeout failures were machine load from parallel agents, not regressions).

wmadden and others added 5 commits June 6, 2026 19:08
…rrow aligned (TML-2771)

Signed-off-by: Will Madden <madden@prisma.io>
…t 2-space indent with arrows aligned (TML-2771)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ht) — RED pins the lane-bleed bug (TML-2771)

Adds `migration-graph-colour-matrix.test.ts` with:

- `assertRowColors` / `lastCodeBefore` helpers for per-cell ANSI assertions
  that bypass NO_COLOR=1 (forced codes still emit \x1b[92m/\x1b[2m).
- Six named fixtures: straightLine, twoBranches, branchPlusRollback, diamond,
  rollbackOnPath, loopViaInvariant, showcase.
- Matrix: normal-rotation × path-highlight-trunk × path-highlight-branch per fixture,
  plus cross-cutting no-rotation-in-path-highlight-mode suite.

2 tests CURRENTLY FAIL (the stage-3 done condition):
  - branchPlusRollback: ∅ landing ╯ carries rotation code (\x1b[35m) in path-highlight
    mode — EMPTY_CONTRACT_HASH is excluded from contractHighlights, so the arc-landing
    node row falls through to laneStylerForColumn (rotation).
  All other 35 tests pass against current src.

The diamond branch/merge connector bleed-guard assertions (╮ and ╯ must be DIM for
off-path col-1) PASS — renderConnectorRow already uses per-column columnHighlights
correctly for forward connectors. The bug is isolated to the ∅ arc-landing row.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…layout (TML-2771)

Add optional `migrationHash` to every routing StructuralCell variant so the
renderer (stage 3) can classify cells by their owning edge rather than by
column index. The field is inert until stage 3 reads it.

- `vertical-pass`, `horizontal-pass`, `branch-tee`, `branch-corner`,
  `merge-tee`, `merge-corner`, `arc-branch-corner`, `arc-branch-tee`,
  `arc-land-corner`, `arc-land-tee`, `arc-land-bridge` all gain
  `migrationHash?: string`.
- `arc-crossing` gains both `migrationHash` (vertical lane owner) and
  `arcMigrationHash` (the arc crossing over it).
- A `laneEdgeByIndex` map threaded through `layoutComponent` tracks the
  last-emitted edge hash per lane; `emitEdgeRow` updates it after every
  emission.
- `buildEdgeCells`, `buildNodeCells`, `buildMergeConnectorCells` read
  from `laneEdgeByIndex` to tag pass-through `vertical-pass` cells.
- `buildBranchConnectorCells` uses pre-computed `fanEdgeHashByLane`
  (first edge per fan group) for tee/corner cells; `trunkEdgeHash` tags
  the start-lane tee.
- `applySkipRollbackRouting` tags every arc cell it writes with
  `route.edge.migrationHash`; `arc-crossing` cells carry the existing
  vertical owner's hash in `migrationHash` and the arc's hash in
  `arcMigrationHash`.
- Normal-mode rotation (resolveRowArcLaneColors / laneColorForColumn)
  reads no new fields; all existing tests + snapshots unchanged.

Co-Authored-By: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
…-path green bleed (TML-2771)

Stage-3: threads per-cell edge identity through every structural cell
(vertical-pass, branch-tee, merge-corner, arc-land-corner, etc.) so each
glyph is coloured by its own edge's on-path/off-path annotation rather than
a column-level aggregate. Off-path rollback arc corners and merge corners that
share a column with an on-path edge no longer bleed green.

The failing test over-asserted: its arcLandingRows filter matched merge-
connector rows (├─╯) in addition to true arc-landing node rows (○◂──╯). The
╯ at col-1 in `├─╯   │ │ │` is the merge-corner for bob_avatar, which is
on-path — green is correct there. Updated the filter to require ◂ (the arc-
landing node marker) so only genuine arc-land-corner cells are checked.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@wmadden

wmadden commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Systematic rebuild of the graph colouring (replacing the whack-a-mole). Three commits:

Root cause: structural lane cells (vertical-pass, branch/merge tees+corners, rollback arcs, crossings) carried no edge identity, so the renderer classified them by column with "on-path wins". Lanes get reused across edges, so any column touched by an on-path edge was stamped entirely green → green bled across off-path branches, merges, and rollback landing arcs (the showcase mess).

Fix (test-first):

  • test(cli) — a real colour test matrix: topologies (straight line · 2 branches · branch+rollback · diamond · rollback-on-path · loop-via-invariant · full showcase) × strategies (normal rotation · path-highlight along trunk · along an alternate branch), with a per-glyph colour-walk assertion (not one-cell spot-checks) that force-colours and reads the actual ANSI.
  • refactor(cli)per-cell edge identity threaded through the layout: every routing cell (incl. rollback arc corners/bridges/crossings) now carries the migration it routes.
  • fix(cli) — the renderer classifies each cell by its own edge and deletes the column-level "on-path wins". Off-path lanes/arcs/connectors → dim; on-path → green. Normal-mode rotation untouched.

Verified by force-rendering the real @db→prod showcase: the previously all-green off-path rollback landing arcs (○◂──────╯) are now dim except the on-path node marker; off-path edge glyphs/hashes are dim. All 1283 colour tests pass; graph/status/list snapshots unchanged. The matrix now guards against regression.

wmadden and others added 5 commits June 6, 2026 21:23
…e continuity in migrate --show (TML-2771)

Four residual mis-tagging bugs identified by the operator's X/Y cell annotations:

Bug X1 (arc-tee connector): renderNodeMarkerPair applied the node's laneOverride
to both the marker (○) and the arc connector (─/◂). When the node is on-path but
the back-arc is off-path, the connector was GREEN. Fixed by threading a separate
arcLaneOverride derived from the arc cell's migrationHash.

Bug X2 (arc-crossing dash in connector): renderConnectorRow applied effectiveOverride
to the full ┼─ pair. Fixed by splitting: ┼ takes the glyph column's override, ─ takes
the dash column's override independently.

Bug Y1 (branch-connector tee hash): emitBranchConnector used laneEdgeByIndex for
the trunk lane, which may hold a skip-rollback's hash emitted just before the
connector. Fixed by preferring fanEdgeHashByLane for the trunk lane, which holds the
downward fanout edge's hash.

Bug Y2 (fan-lane pass-through hash): When a node fans out into multiple groups,
laneEdgeByIndex was not populated for fan lanes before their edge rows were emitted.
Pass-through cells in peer group rows carried no hash, falling through to an incorrect
override. Fixed by pre-populating laneEdgeByIndex for every fan lane from
fanEdgeHashByLane before any edge rows are emitted.

Tests: update branch-connector tee identity assertion (now carries fanout hash, not
undefined); add fan-lane pass-through identity test; add arc-tee connector colour,
branch-connector tee colour, and arc-crossing colour assertions to colour matrix.

Signed-off-by: Will Madden <madden@prisma.io>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rate --show graph (TML-2771)

Two colour-bleed bugs in the path-highlight mode of the migration graph renderer:

BUG 1 — arc-crossing bridge cells (rendered as ──) in node/edge rows were coloured
by migrationHash (the vertical lane owner) instead of arcMigrationHash (the arc
that bridges horizontally). When the vertical lane was on-path and the arc was
off-path, the bridge rendered green instead of dim. Fixed by preferring arcMigrationHash
for arc-crossing cells in all three per-cell hash selection sites in renderMigrationGraphTree.

BUG 2 — The trailing ─ dash after a branch-tee or merge-tee junction in connector
rows was coloured by columnLaneOverride?.get(dashColumn) (the next column's annotation)
rather than the tee cell's own effectiveOverride. When the tee's edge was off-path
but the adjacent column was on-path, the dash bled green. Fixed by using effectiveOverride
for both the junction glyph and its trailing dash.

New matrix test assertions cover both fixes: off-path tee trailing dash stays dim when
adjacent column is on-path; on-path tee trailing dash is green; arc-crossing bridge in
node rows is dim when the arc edge is off-path even when the vertical lane is on-path.

Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-preview-the-path-ordered-migrations-migrate

Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts
…erge (TML-2771)

Main renamed the migration-list space-entry field from `spaceId` to
`space`. Update the isAppSpace computation in migration-status and the
appSpaceId test fixtures (space + name/fromContract/toContract) to match
the merged schema.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…markers (TML-2771)

Verified the migration-status diagnostics e2e against actual rendered
output: reserved markers render as @contract and @contract @db (db),
not the old angle-bracket form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden wmadden merged commit 10c7daf into main Jun 7, 2026
21 checks passed
@wmadden wmadden deleted the tml-2771-migrate-show-preview-the-path-ordered-migrations-migrate branch June 7, 2026 08:44
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