Skip to content

TML-2746: condensed annotated-tree migration graph renderer (--tree)#658

Merged
wmadden merged 46 commits into
mainfrom
tml-2746-redesign-migration-graph-tier-3-condensed-annotated-node
Jun 1, 2026
Merged

TML-2746: condensed annotated-tree migration graph renderer (--tree)#658
wmadden merged 46 commits into
mainfrom
tml-2746-redesign-migration-graph-tier-3-condensed-annotated-node

Conversation

@wmadden-electric
Copy link
Copy Markdown
Contributor

@wmadden-electric wmadden-electric commented May 31, 2026

Linked issue

Refs TML-2746.

Follow-up: TML-2748 migrates migration status and the default migration graph output onto this renderer and deletes the dagre dependency.

At a glance

prisma-next migration graph --tree draws the whole contract/migration graph as a condensed, annotated tree — one migration per line carrying from → to, contract hashes as nodes, full routed back-arcs for rollbacks, and ref/DB/contract overlays:

│⟲            reapply_noop        7777777 → 7777777
○             7777777
│↑            hotfix              6666666 → 7777777
○─────╮       6666666
│     │↓      rollback_users      6666666 → 2222222
│↑    │       add_comments        5555555 → 6666666
│↓    │       rollback_posts      6666666 → 5555555
○     │       5555555
│↑    │       add_posts           4444444 → 5555555
○     │       4444444
├─┬─╮ │
│↑│ │ │       merge_alice         33aaaaa → 4444444
│ │↑│ │       merge_bob           33bbbbb → 4444444
│ │ │↑│       fast_forward        1111111 → 4444444
○─┼─┼─┼─╮     33aaaaa
│ │ │ │ │↓    rollback_alice      33aaaaa → 1111111
│↑│ │ │ │     alice_phone         2222222 → 33aaaaa
│ ○ │ │ │     33bbbbb
│ │↑│ │ │     bob_avatar          2222222 → 33bbbbb
├─╯ │ │ │
○◂──┼─╯ │     2222222
│↑  │   │     add_users           1111111 → 2222222
├───╯   │
○◂──────╯     1111111
│↑            init                ∅ → 1111111
∅

is a forward migration, a rollback, a self-edge; is a contract node, the empty/genesis contract. Tips are at the top, roots at the bottom (git log --graph direction). --ascii renders the same graph with single-width ASCII glyphs for non-UTF terminals and CI.

The old dagre node-graph remains the default migration graph output; --tree is the opt-in successor this PR introduces.

Decision

This PR ships a new Tier-3 renderer for migration graph, exposed behind --tree (with --ascii), that replaces the sprawling dagre layout with a condensed annotated tree in the same visual language as migration list --graph. It carries four substantive pieces:

  1. A new rendering pipeline — a tolerant topology classifier, a row model, a lane-allocation layout model, and a glyph renderer — that draws contracts as nodes and migrations as labelled edges, with routed back-arcs for rollbacks all the way to their target.
  2. A clean-room lane allocator built around the canonical git log --graph lane-join algorithm, replacing an earlier imperative special-case allocator that did not survive the graph's edge cases.
  3. Longest-forward-path vertical layering that keeps tips above roots even under convergence, forward cross-links, and node-skipping rollbacks — where the previous DFS post-order tangled.
  4. A reference doc (docs/reference/migration-graph-rendering.md) capturing the rendering contract, glyph palette, worked topologies, and the allocator state machine.

How it fits together

  1. Classify the edges (migration-list-graph-topology.ts). The shared tolerant classifier — already used by migration list --graph — is extended with a peel pass that demotes node-skipping rollbacks so the forward subgraph is provably acyclic. Every edge is forward / rollback / self; convergence and divergence are counted over the forward subgraph only.
  2. Build the row model (migration-graph-rows.ts). Edges become ordered rows. Vertical order is a longest-forward-path layering (tips first, last), and a detached current contract is surfaced as a floating node so the post-merge "your combined contract" state is always visible.
  3. Allocate lanes (migration-graph-layout.ts). A deterministic lane-join state machine assigns each row a lane and emits structural cells (nodes, vertical/horizontal passes, branch/merge tees and corners). Node-skipping rollbacks are routed as real back-arcs into their own back-lane, with crossing characters where they span live lanes.
  4. Render glyphs (migration-graph-tree-render.ts). Structural cells become box-drawing glyphs against a Unicode or ASCII palette, with the from → to data column, ref/DB/contract overlays aligned to that column, and the empty contract drawn as the glyph itself.
  5. Wire the command (migration-graph.ts). --tree routes the contract-space aggregate through the new pipeline and emits to stdout without the Clack rail; --ascii forces the ASCII palette.

Reviewer notes

  • Largest diff is migration-graph-layout.ts (~1.1k lines). It's the lane allocator + routed-arc machinery. The load-bearing parts are the lane-join placement, the back-arc tee/landing rows, and the crossing detection (an active lane is any non-empty / non-horizontal-pass cell, plus any back-arc route that crosses the row). The comprehensive golden in migration-graph-rows.test.ts is the best place to read the output end-to-end.
  • The dagre renderer is untouched and still the default. This PR adds --tree alongside it; it does not remove graph-render.ts or the dagre dependency. Migrating the default and deleting dagre is TML-2748.
  • migration-graph-layout.test.ts keeps a self-contained renderLayout debug helper with its own glyph rendering (so its ○ … ∅ goldens intentionally differ from the production renderer's ). It exists to make the structural-cell layout legible in tests; it is not the production path.
  • Transient project artefacts under projects/migration-graph-rendering/ (spec, plan, mockups, prototype gallery, learnings) are left on disk for review. Close-out will migrate the durable design into docs/ and delete the rest.
  • Determinism caveat is documented inline on the classifier's peel pass: the first peel branch + single-edge-per-iteration breaks every cycle deterministically; the marginal-edge fallback is only reached on still-cyclic candidate sets.

Testing performed

Run on final HEAD (ae40aa903):

  • pnpm vitest run on the four graph formatter suites — 81 passed (migration-graph-layout, migration-graph-rows, migration-graph-tree-render, migration-list-graph-topology).
  • pnpm typecheck (cli package) — clean.
  • pnpm biome check on touched files — clean; bare-cast ratchet delta 0.
  • Full cli package suite green; the only "failures" are pre-existing spawn-subprocess timeouts under parallel load (version.test.ts, removed-verb-redirects.test.ts) that pass in isolation.

Follow-ups

  • TML-2748: migrate migration status off dagre, make the condensed tree the default migration graph output, and delete the dagre dependency.

Alternatives considered

  • Reuse the dagre node-graph renderer. Rejected: the dagre output is large, struggles even on simple histories, and can't show full routed back-arcs in the condensed line-per-migration shape we want. The tree shares the migration list --graph visual language instead.
  • Patch the original imperative lane allocator. Rejected after it grew into a special-case zoo that still failed several topologies; a clean-room rewrite around the canonical git log --graph lane-join algorithm was smaller and correct.
  • DFS post-order for vertical ordering. Rejected: it tangled tips and roots under cross-links and branch-originating rollbacks. Longest-forward-path layering is order-independent and keeps the tips-above-roots invariant.
  • Draw the empty contract as ○ … ∅ (node glyph + hash label). Rejected: the crossed-circle is itself the node marker for the empty contract, so the row collapses to .

Skill update

n/a — internal renderer + new experimental CLI flags. The new --tree / --ascii flags are documented in the migration graph command's long description and in docs/reference/migration-graph-rendering.md; no user-facing skill file changes.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is in TML-NNNN: <sentence-case title> form.
  • The Skill update section above is filled in.

Summary by CodeRabbit

  • New Features

    • New condensed annotated tree renderer for migration graphs (--tree) and pipe-friendly ASCII mode (--ascii); ref styling now highlights a "contract" marker.
  • Documentation

    • Added complete tree-view reference: glyphs, column formats, rendering rules, examples, and relationship to other views.
  • Tests

    • Extensive suites validating row/ grid layout, routing, and Unicode/ASCII rendering.
  • Chores

    • Added many generated migration fixtures and a showcase demo config.

wmadden added 30 commits May 30, 2026 20:57
…2746)

Settle the visual language for the redesigned `migration graph` command:
a condensed, deterministic, annotated node-link diagram drawing contracts as
nodes and migrations as edges, with complete back-edges, replacing the dagre
layout and its golden-path root/tip selection.

- mockups.md: the locked design of record — hand-drawn layouts across the full
  fixture set plus synthetic pathologicals, with the layout rules the renderer
  must implement (lane direction glyphs, adjacency-decides-connector, forward
  edges fold into the spine, non-adjacent rollbacks as teed-off arcs).
- prototype/: the zero-build harness used to reach the design.

Sits on the consolidated tolerant graph model (TML-2739).

Signed-off-by: Will Madden <madden@prisma.io>
…Tier 3 design

Reuse the `migration list --graph` `(refs)` decoration for the three "where
am I" overlays on the node graph, instead of the old `migration graph`
per-marker glyph tags (◆ db / ◇ contract / rotating ref colours):

- Refs, the live DB marker, and the current emitted contract all decorate the
  node row as one trailing `(name, …)` parenthetical, with `db` and `contract`
  reserved names sharing the parens alongside user refs.
- A detached current contract (changed but not yet planned) is a floating node
  carrying `(contract)` — no phantom dashed connector, since an edge here is a
  migration and there is none.

Updates rule 2, adds rule 10, and adds a worked node-overlays section.

Signed-off-by: Will Madden <madden@prisma.io>
…ned-root tags

- Refs/(db)/(contract) on node rows now start at the from → to data column,
  not flush against the hash.
- Remove the "(root: parent pruned)" in-diagram annotations from the disjoint-
  forest and dangling-parent examples; the missing ∅ root already says it.

Signed-off-by: Will Madden <madden@prisma.io>
…ration-graph-tier-3-condensed-annotated-node
…ration-graph-tier-3-condensed-annotated-node
…learnings ledger

Pins the implementation architecture (tolerant MigrationGraph → row model →
grid → text → arcs → overlays → ASCII), scope (rewire `migration graph` only;
leave dagre for `migration status` → TML-2748), and the six-dispatch plan.

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

Extract the forward/rollback/self DFS classification into an internal
classifyNormalizedEdges() function that accepts a normalized edge shape
independent of the calling tier.

classifyMigrationListGraphTopology (Tier-2) now normalizes MigrationListEntry[]
and delegates to the shared core — existing behaviour is byte-identical.

Add classifyMigrationGraphTopology(MigrationGraph) as a new export that
normalizes the graph edge set and delegates to the same core, so Tier-2 and
Tier-3 cannot diverge on forward/rollback/self classification.

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

New module migration-graph-rows.ts implements the first pure-data stage of
the migration graph render pipeline:

- buildMigrationGraphRows(graph: MigrationGraph): MigrationGraphRowModel
  Classifies every edge as forward/rollback/self (via the shared DFS in
  classifyMigrationGraphTopology) and produces a deterministic vertical node
  ordering: roots at the bottom, tips at the top, disjoint components stacked
  and separated by null sentinels.

- ClassifiedEdge: migrationHash + from + to + dirName + kind — the resolved
  edge shape the column allocator will consume.

- MigrationGraphRowModel: ordered nodes[], all edges[], and edgesByFrom /
  edgesByTo lookup maps.

Node ordering algorithm: DFS post-order over forward edges from roots
(EMPTY_CONTRACT_HASH first, then lex). Disjoint components are identified via
weak connectivity and ordered EMPTY-first then by lex-smallest node hash.
Pure cycles fall back to lex-sorted root seeding, matching the topology pass.

22 tests cover: linear, rollback, diamond, disjoint forest, cross-link,
self-edge, pure cycle, classified-edge fields, lookup maps, and determinism.

Signed-off-by: Will Madden <madden@prisma.io>
Allocate git-log-style lanes and connector rows from the D1 row model,
classifying edges as adjacent vs node-skipping without emitting glyphs.

Signed-off-by: Will Madden <madden@prisma.io>
Replace fixture-specific convergence heuristics with git-log-style lane
assignment: preset branch columns/spine lanes at divergence, offset
convergence fans by active pass-through lanes, emit all fan producers
before visiting subtrees, and derive merges from lane-wanting state or
recorded producer/child columns. Assert cross-link and kitchen-sink mockup
layouts; place self-edge rows immediately above their node.

Signed-off-by: Will Madden <madden@prisma.io>
Rewrite the cross-link and kitchen-sink layout tests to assert the exact
geometry hand-derived from mockups.md (columns, per-edge lane + pass-through,
connector spans, connector cell roles, and kitchen-sink row ordering) rather
than the allocator's current output, and skip them until the allocator
produces that geometry. This breaks the tautological-test pattern: the
assertions are now the design-of-record, not a mirror of the code.

cross-link: D moves to its own column (it is both a divergence child of A and
a convergence producer into E), lane 2 closes at D, the C-spine no longer
carries a spurious pass-through above E, and connector rows assert
column-absolute cells with pass-throughs for active lanes outside their span.

kitchen-sink: the longer branch must render contiguously before the shorter
sibling (no braiding); asserts full node-row ordering plus the absence of
lane-1 pass-throughs on long-branch edges.

The eight faithful fixtures (linear, diamond, sequential-diamonds, 3-way fan,
self-edge, disjoint, rollback marking, structural roles) keep running and
pass; the two rewritten fixtures are skipped pending implementation.

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

Two corrections to the locked layout suite surfaced by review:

- Convergence-producer adjacency was inverted. 'adjacent' is the producer
  whose source node is the nearest node row below the fan (the diamond's
  alice/bob reading). In cross-link, B (B→E's source) renders immediately
  below the fan and D (D→E's source) further down, so B→E is adjacent and
  D→E is node-skipping-forward — the reverse of what was asserted.

- merge@A's `├───┘` has a dormant lane 1 between its producers (lanes 0 and
  2). The role vocabulary had no horizontal-fill, so the bridging `─` could
  not be pinned; introduce a `horizontal-pass` role and assert cells[1], so
  an implementation cannot emit a false join there and pass.

Both fixtures remain skipped pending implementation; suite stays 8 pass / 2 skip.

Signed-off-by: Will Madden <madden@prisma.io>
Kitchen-sink layout now walks each tip up its spine until the next divergence,
visits longer tips first, and uses asymmetric pass-through on unequal fan-outs
so lane 0 runs alone until the short branch opens.

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

Adds a test-only debug renderer that maps the structural cell roles back to
the box-drawing glyphs from mockups.md, so each layout fixture carries an
inline snapshot of the actual diagram next to its geometry assertions. The
renderer immediately surfaced two model defects the structural assertions
missed:

- node rows whose lane opened late (kitchen-sink tip_short) were missing their
  node cell; fixed by ensuring grid width covers the node column before
  building its cells.
- adjacent rollbacks leave their lane reserved, spilling the forward edge below
  into a phantom second lane (annotated FIXME on both rollback snapshots;
  deferred to rollback-lane-lifecycle work).

Declares the horizontal-pass cell role referenced by the locked cross-link
spec.

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

Reserve a gap lane for cross-link divergences (B spine at 0, D at 2, B→E at 1),
emit full-width branch/merge connector cells with vertical-pass and horizontal-pass,
and align convergence-producer adjacency with the mockup.

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

Replaces the rollback fixtures' inline snapshots that pinned the known-wrong
phantom-lane output with `it.fails` tests asserting the correct single-lane
rendering (per mockups.md). The expected-vs-actual diff is now the spec for the
rollback-lane-lifecycle fix; when that lands, `it.fails` flips red and forces
conversion to `it`. Geometry assertions stay in their own passing test.

Signed-off-by: Will Madden <madden@prisma.io>
Add mockups and it.fails tests for the divergence-heavy topologies the
current lane allocator gets wrong: multi-edge (parallel migrations between
one pair), wide-fan (pure N-way divergence), sub-branches (nested
divergence reusing lanes), diamond-sub-branch (diamond with a leaf spur),
and complex (divergence + diamond + spine + leaf tip).

Each test pins the correct node columns, connector placement, and per-edge
lane staircase from mockups.md via spacing-free geometry assertions; forward
adjacency is intentionally not asserted (forward edges always render the same
glyph). The tests fail today and flip to it() once the allocator generalises
to the lane-join rule.

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

The previous allocator grew a special-case zoo (cross-link presets, divergence
child-lane heuristics, spine-branch indices, phantom-lane bookkeeping) and still
mis-laid divergence-heavy shapes: wide fans, nested divergence, diamonds with
leaf spurs, parallel multi-edges. Patching it further was untenable.

Replace it with one rule for every topology, adapted from the git-graph lane
algorithm. Nodes are emitted tips-first in DFS post-order (input-order child
visitation, so a node lands below every branch that feeds it). Each lane "wants"
a target contract; when that contract is reached, the lanes wanting it converge
(merge connector). A node's forward producers are grouped by source: distinct
sources fan (branch connector), parallel multi-edges to one source stack in a
single lane. Rollbacks decorate their node's own column and never reserve a
downward lane, so adjacent and node-skipping rollbacks both stay single-lane.

All divergence-family, multi-edge, and rollback cases that were locked as
expected-failures now pass and are flipped to live tests; cross-link,
kitchen-sink, and the convergence fixtures keep their exact geometry. Net
-228 lines with the special-casing gone.

Signed-off-by: Will Madden <madden@prisma.io>
Renders the tier-3 migration graph as the condensed annotated tree from
the locked mockups: contracts as `○ <hash>` rows, migrations as labelled
`│↑`/`│↓`/`⟲` lane rows, branch/merge connectors, and `(refs, db,
contract)` node overlays reusing the `migration list --graph` decoration.

Wires it behind an experimental `--tree` flag; the default dagre path is
unchanged. The data column reserves a trailing gap past the longest
dirName so long migration names keep whitespace before `hash → hash`
instead of colliding with the source hash.

Signed-off-by: Will Madden <madden@prisma.io>
The tree renderer routed through `ui.log` (clack's `log.message`), which
prefixes every line with the prompt-flow `│  ` rail — a vertical bar that
runs parallel to, and visually competes with, the graph's own lane
glyphs. No input is being solicited, so the rail is incidental styling
from the wrong sink.

Emit the rendered tree and its summary via `ui.output` to stdout instead,
matching `migration list --graph`. The graph is the command's result, so
it belongs on stdout and carries only its own box-drawing.

Signed-off-by: Will Madden <madden@prisma.io>
When the working schema emits a contract hash no migration produces,
prepend it as a single-node component at the top of the row model so
the tree renderer can overlay (contract) without a phantom edge.

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

Addresses review nits on the detached-contract floating node: the
three-part "is this contract detached" guard was duplicated across the
empty-graph and prepend paths — extract it into a single
`detachedContractHash` helper that returns the narrowed hash. Add the
missing test for an empty graph whose working schema already emits a
detached contract (lone floating node, no edges).

Signed-off-by: Will Madden <madden@prisma.io>
The fixture refs lived at `migration-fixtures/<topology>/refs/`, but the
contract-space loader reads a space's refs from
`migrations/<space>/refs/` (the app space being `<topology>/app/refs/`).
A top-level `refs/` is deliberately ignored by the space enumerator, so
every fixture's refs (prod, staging, …) silently failed to load and no
`(refs)` overlay ever appeared in migration graph/list/status for the demo.

Relocate each fixture's `refs/` under `app/refs/` so the refs load and the
overlays render.

Signed-off-by: Will Madden <madden@prisma.io>
A node immediately followed by a branch connector was placing its hash at
the fanned-out branch width, while a convergence base placed its hash at its
own single lane. That asymmetry indented branch apexes (e.g. a diamond top)
one lane too far, so they failed to line up with linear nodes and the
floating contract node above them. Use the node row width for both cases so
an apex and a base sit in the same column.

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

Match the design-of-record to the corrected renderer: a branch apex sits in
its own lane (col 4 for a lane-0 apex), symmetric with a convergence base,
rather than reserving the fanned-out branch width.

Signed-off-by: Will Madden <madden@prisma.io>
Replace the deferred single-lane node-skipping rollback assertion with the
routed-arc target from mockups.md (source tees ○─╮, back-lane, target lands
○◂╯), plus the two-overlapping-arc case with crossing ┼ and horizontal
landing ○◂──╯. Both it.fails until the routing feature lands.

Signed-off-by: Will Madden <madden@prisma.io>
Allocate back-lanes right of forward lanes with interval coloring for
overlapping spans, tee from source nodes, land on targets with crossings
and bridges, and emit matching glyphs in the tree renderer.

Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
The node-skipping-arc work changed the non-arc edge-label gutter test from
`index > laneIndex` to `index !== laneIndex`. Arc graphs short-circuit on the
wide label column before reaching this branch, so the change only affects
non-arc rendering and is unrelated to routed arcs. Revert to the original
condition; the full formatter suite stays green.

Signed-off-by: Will Madden <madden@prisma.io>
Address the routed back-arc review:

- Replace the three readonly-stripping `as` casts with a local MutableGridRow
  type so the routing pass holds StructuralCell[] directly (cast count back to
  the origin/main baseline).
- Fix nested skip-rollback landings: emit `arc-crossing` (┼) instead of a bare
  `arc-land-bridge` (──) when the bridged lane still carries an active arc, so a
  nested arc lands as `○◂┼─╯`. Lock with a nested-span layout golden.
- Support co-sourced skip rollbacks via an `arc-branch-tee` (┬) role: arcs teeing
  off the same node share a `○─┬─╮` junction. Fill the arc body from below the
  source tee (skipping the labelled edge row) and mutate the edge row in place so
  a co-sourced inner lane is never clobbered. Lock with a co-sourced golden.
- Comment the wide-grid label-column / dirName-gap spacing constants.

Signed-off-by: Will Madden <madden@prisma.io>
wmadden added 7 commits May 31, 2026 15:09
Mirror Tier-2 glyph-mode wiring: palette indirection in the tree renderer,
--ascii on migration graph --tree, ASCII gallery goldens, and reference doc.

Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: Will Madden <madden@prisma.io>
Replace DFS post-order row layering with longest-path ranks on the forward
subgraph so diamonds, cross-links, and rollbacks keep tips at the top. Fix
topology classification: GRAY back-edges only for immediate DFS parents, plus
a peel pass for node-skipping rollbacks that would otherwise stay forward.

Signed-off-by: Will Madden <madden@prisma.io>
Routed node-skipping rollback arcs that span a divergence region were
rendered with gaps: the tee/landing rows left the horizontal run between
the node and the back-lane corner blank and never crossed live forward
lanes, and the branch connector row dropped a pass-through back-arc lane
whenever a later row widened the grid.

Fill the horizontal connector on tee rows and cross any active lane
(forward vertical or another arc) with a junction on both tee and landing
rows; render connector rows from their actual cells and pad trailing
columns so pass-through lanes survive a post-emit grid-width increase.

Locks the behaviour with a comprehensive annotated-tree golden covering a
diamond, a forward cross-link, and overlapping node-skipping rollbacks.

Signed-off-by: Will Madden <madden@prisma.io>
- Exclude arc-land-bridge from the tee-row crossing check, mirroring the
  landing-row check, so a node that is both a skip-rollback source and
  another skip-rollback target on the same row is not over-crossed.
- Document the peel-pass determinism guarantee on shouldPeelForwardEdge.
- Correct the stale "same algorithm as Tier-2" classifier comment.

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 May 31, 2026 16:09
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 4d7c9558-b9cc-4557-8c61-a11b2122b353

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba964f and 9767c3a.

📒 Files selected for processing (31)
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0719_init/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_add_name/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_add_name/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_alice_phone/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_alice_phone/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_bob_avatar/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_bob_avatar/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_bio/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_bio/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_locale/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_locale/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_fast_forward/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_fast_forward/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_alice/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_alice/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_bob/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_bob/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_hotfix/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_hotfix/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_alice/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_alice/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_locale/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_locale/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_users/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_users/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_experiment/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_experiment/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_revert_experiment/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_revert_experiment/start-contract.json
  • examples/prisma-next-demo/showcase-contract/showcase.json
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts
💤 Files with no reviewable changes (1)
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts
✅ Files skipped from review due to trivial changes (27)
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_alice/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_fast_forward/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_alice_phone/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0719_init/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_bob/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_alice/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_experiment/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_hotfix/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_alice/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_bob/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_experiment/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_revert_experiment/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_bob_avatar/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_locale/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_bio/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_users/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_hotfix/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_users/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_bio/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_add_name/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0727_rollback_locale/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_locale/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_add_locale/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_add_name/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_merge_alice/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_bob_avatar/end-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0726_fast_forward/start-contract.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/prisma-next-demo/showcase-contract/showcase.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0725_alice_phone/start-contract.json
  • examples/prisma-next-demo/migration-fixtures/showcase/app/20260601T0730_revert_experiment/start-contract.json

📝 Walkthrough

Walkthrough

Adds a condensed-tree migration-graph renderer: shared topology classifier, deterministic row model, lane-based grid layout with rollback routing, Unicode/ASCII text renderer, CLI flags --tree/--ascii, tests, and specification docs.

Changes

Condensed Annotated Tree Rendering

Layer / File(s) Summary
Edge topology classification for both tiers
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-list-graph-topology.ts
Refactors topology classification into a shared normalized-edge classifier with DFS-based forward/rollback/self discrimination and deterministic root/depth computation.
Row model building and component ordering
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-rows.ts
Builds a deterministic row model: weakly-connected component separation, tips-first node layering, classified edges, lookup maps, and optional detached contract-node prepending.
Grid layout with lane allocation and arc routing
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-layout.ts
Converts rows to a grid: lane allocation, node/edge/connector rows, adjacency refinement, and routed node-skipping rollback back-arcs with tees/corners/bridges/crossings.
Text rendering with glyphs and styling
packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-tree-render.ts
Renders grid to colored text using Unicode or ASCII palettes, maps structural cells to glyphs, formats edge/hash labels, and aligns output with terminal-aware padding.
CLI wiring for --tree and --ascii flags
packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts
Adds --tree and --ascii options; when --tree is used builds rows/layout and emits the annotated tree via the renderer, otherwise falls back to prior graph rendering.
Grid layout test suite
packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-layout.test.ts
Comprehensive Vitest coverage for buildMigrationGraphLayout across linear, diamond, fan, nested, cross-link, multi-edge, disjoint, and rollback routing scenarios with inline snapshots and geometry assertions.
Row model test suite
packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-rows.test.ts
Vitest coverage for buildMigrationGraphRows: empty graph, deterministic ordering, edge classification, component separation, detached contract handling, cycles, and a comprehensive fixture with final rendered-string assertion.
Text rendering test suite
packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts
Tests for renderMigrationGraphTree in Unicode and ASCII modes asserting exact outputs for multiple topologies and overlay combinations.
Rendering specification
docs/reference/migration-graph-rendering.md
Documentation specifying the --tree rendering contract: row/grid model, gutter spine glyph semantics, data-column formats, Unicode/ASCII palettes and CLI glyph-mode resolution, examples, and golden tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • prisma/prisma-next#644: Overlaps migration-graph command wiring and data-loading execution paths; both touch migration-graph command handling.

Suggested reviewers

  • aqrln

Poem

🐰 I hop through lanes where migrations bloom,
Glyphs trace arcs across the room,
From tips to roots the tree unfolds,
Unicode or pipes — both told. 🌿✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2746-redesign-migration-graph-tier-3-condensed-annotated-node

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 31, 2026

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 9767c3a

…ration-graph-tier-3-condensed-annotated-node
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 31, 2026

size-limit report 📦

Path Size
postgres / no-emit 135.88 KB (0%)
postgres / emit 125.59 KB (0%)
mongo / no-emit 75.69 KB (0%)
mongo / emit 70.68 KB (0%)

The migration graph tree renderer dimmed the whole glyph pair for nodes
serving as back-arc landings (○◂) or tees (○─), so their node marker
looked dim while plain nodes (○) stayed bright. Edge arrows (↑/↓/⟲) were
likewise dimmed along with their lane.

Split the marker pair so the contract-node marker and the direction arrow
route through the bright signal channel, while the arc connector, landing
arrowhead, and vertical lane stay dim. No-color output is byte-identical
since both channels are identity without color.

Signed-off-by: Will Madden <madden@prisma.io>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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/commands/migration-graph.ts (1)

121-148: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Let --tree override auto-JSON.

flags.json is auto-enabled on non-TTY stdout, so prisma-next migration graph --tree --ascii | cat currently emits JSON instead of the explicit tree format. --tree is a user-selected renderer and should be handled before the auto-JSON branch, while still letting an explicit --json flag win.

Suggested fix
-        } else if (flags.json) {
+        } else if (flags.json && !options.tree) {
           const nodes = [...graphResult.graph.nodes];
           const edges = [...graphResult.graph.migrationByHash.values()].map((e) => ({
             dirName: e.dirName,
             from: e.from,
             to: e.to,
             migrationHash: e.migrationHash,
           }));
           ui.output(
             JSON.stringify({ ok: true, nodes, edges, summary: graphResult.summary }, null, 2),
           );
-        } else if (!flags.quiet) {
-          if (options.tree) {
+        } else if (!flags.quiet) {
+          if (options.tree) {
             const refsByHash = new Map<string, string[]>();
             for (const ref of graphResult.refs) {
               const existing = refsByHash.get(ref.hash);
               refsByHash.set(ref.hash, existing ? [...existing, ref.name] : [ref.name]);
             }

Also applies to: 168-176

🤖 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/migration-graph.ts` around
lines 121 - 148, Reorder the renderer checks inside the handleResult callback so
the explicit render option --tree (options.tree) is evaluated before the
auto-JSON branch (flags.json) — but still prefer JSON when the user explicitly
passed --json (use the same explicit-flag detection used elsewhere in the CLI);
keep the existing special-case for options.dot first (in the same handleResult
callback where options.dot, flags.json, flags.quiet and options.tree are
handled) and ensure flags.quiet logic remains unchanged.
🤖 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 226-231: The function abbreviateHash currently calls
abbreviateContractHash (which already truncates to MIGRATION_LIST_HASH_WIDTH)
then slices by hashLength, so callers passing hashLength >
MIGRATION_LIST_HASH_WIDTH get a misleading result; fix this by clamping
hashLength to the supported range before slicing — e.g. compute const maxLength
= Math.min(Math.max(1, hashLength), MIGRATION_LIST_HASH_WIDTH) and then return
abbreviated.slice(0, maxLength) (leave the EMPTY_CONTRACT_HASH check and
abbreviateContractHash call as-is), referencing abbreviateHash,
abbreviateContractHash, MIGRATION_LIST_HASH_WIDTH and EMPTY_CONTRACT_HASH.

---

Outside diff comments:
In `@packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts`:
- Around line 121-148: Reorder the renderer checks inside the handleResult
callback so the explicit render option --tree (options.tree) is evaluated before
the auto-JSON branch (flags.json) — but still prefer JSON when the user
explicitly passed --json (use the same explicit-flag detection used elsewhere in
the CLI); keep the existing special-case for options.dot first (in the same
handleResult callback where options.dot, flags.json, flags.quiet and
options.tree are handled) and ensure flags.quiet logic remains unchanged.
🪄 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: 62f583e1-9e2c-4674-949c-a56652c3690d

📥 Commits

Reviewing files that changed from the base of the PR and between f779815 and a242ce6.

⛔ Files ignored due to path filters (6)
  • projects/migration-graph-rendering/README.md is excluded by !projects/**
  • projects/migration-graph-rendering/learnings.md is excluded by !projects/**
  • projects/migration-graph-rendering/mockups.md is excluded by !projects/**
  • projects/migration-graph-rendering/prototype/gallery.md is excluded by !projects/**
  • projects/migration-graph-rendering/prototype/proto.mjs is excluded by !projects/**
  • projects/migration-graph-rendering/spec.md is excluded by !projects/**
📒 Files selected for processing (31)
  • docs/reference/migration-graph-rendering.md
  • examples/prisma-next-demo/migration-fixtures/complex/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/complex/app/refs/staging.json
  • examples/prisma-next-demo/migration-fixtures/converging-branches/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/diamond-sub-branch/app/refs/experiment.json
  • examples/prisma-next-demo/migration-fixtures/diamond-sub-branch/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/diamond/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/kitchen-sink/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/linear/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/long-spine/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/long-spine/app/refs/staging.json
  • examples/prisma-next-demo/migration-fixtures/multi-branch/app/refs/feature.json
  • examples/prisma-next-demo/migration-fixtures/multi-branch/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/multi-branch/app/refs/staging.json
  • examples/prisma-next-demo/migration-fixtures/multi-rollback-branch/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/multi-rollback-branch/app/refs/staging.json
  • examples/prisma-next-demo/migration-fixtures/rollback-continue/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/sequential-diamonds/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/single-branch/app/refs/prod.json
  • examples/prisma-next-demo/migration-fixtures/single-branch/app/refs/staging.json
  • examples/prisma-next-demo/migration-fixtures/sub-branches/app/refs/experiment.json
  • examples/prisma-next-demo/migration-fixtures/sub-branches/app/refs/feature.json
  • examples/prisma-next-demo/migration-fixtures/sub-branches/app/refs/prod.json
  • packages/1-framework/3-tooling/cli/src/commands/migration-graph.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-layout.ts
  • packages/1-framework/3-tooling/cli/src/utils/formatters/migration-graph-rows.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-graph-topology.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-layout.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-rows.test.ts
  • packages/1-framework/3-tooling/cli/test/utils/formatters/migration-graph-tree-render.test.ts

wmadden added 3 commits June 1, 2026 08:34
The ∅ (empty contract) source glyph rendered unpadded in migration graph
tree edge rows (∅ → hash), so its arrow sat one column left of the 7-char
hash sources above and below it. Pad the empty source to the hash-column
width, matching the flat migration list, so every → / -> lines up.

Signed-off-by: Will Madden <madden@prisma.io>
Records a second slice in the migration-graph-rendering project: now that
the Tier-3 tree renderer is compact and correct, the Tier-2 list --graph
gutter is the redundant middle. Captures the removal design, grounded
footprint, and a two-dispatch plan; multi-space graph visibility left as
an open question.

Signed-off-by: Will Madden <madden@prisma.io>
The graph ref overlay previously bolded the live-database `db` marker as a
special case while every other ref rendered plain green. `db` is just another
ref and does not warrant that treatment.

Emphasize the `contract` marker instead: it is not a stored ref but the user's
declared desired state and the implicit base/target for `plan` / `migrate`, so
bold is principled rather than arbitrary. The active-ref bold is unchanged.

Signed-off-by: Will Madden <madden@prisma.io>
wmadden added 4 commits June 1, 2026 09:32
Adds a deliberately comprehensive migration-graph fixture that exercises
every rendering shape the Tier-3 migration graph renderer handles:

- Linear spine (init → add_name → merge → add_bio → add_locale → hotfix)
- Diamond divergence/convergence (alice_phone + bob_avatar branches from
  add_name, both converging at merge_alice/merge_bob)
- Forward cross-link (fast_forward: init → merge, skipping B/ALICE/BOB)
- Adjacent rollback (rollback_locale: D → C)
- Node-skipping rollbacks (rollback_users: D → B; rollback_alice: ALICE → A)
- Self-edge with data op (reapply_noop: E → E, verifies verified=true)
- Disjoint 2-cycle component ({experiment, revert_experiment} disconnected
  from the ∅ root after deleting the temporary bridge migration)
- Refs overlay: prod → E (hotfix), staging → D (add_locale)
- Contract overlay: (contract) lands on E (hotfix), the emitted state

The contract is left emitted at state E so the (contract) overlay aligns
with the prod ref. The (db) overlay is not included as it requires a live
DB connection (offline mode only).

Schema: single `account` model gaining nullable columns (name, phone,
avatar, bio, locale) plus `verified Boolean @default(true)` in hotfix.
The disjoint component adds a `widget` model disconnected from ∅.

Signed-off-by: Will Madden <madden@prisma.io>
After merging main (TML-2751 symmetric domain plane), the showcase
migration fixture contracts still used top-level `models`, which fails
the postgres snapshot-read-shapes scan. Move models under
domain.namespaces.__unbound__ without changing storage hashes.

Signed-off-by: Will Madden <madden@prisma.io>
abbreviateHash routed through abbreviateContractHash first, which
already capped output at MIGRATION_LIST_HASH_WIDTH and made hashLength
> 7 a no-op. Slice the stripped digest directly so the option works.

Signed-off-by: Will Madden <madden@prisma.io>
@wmadden wmadden merged commit 67633a7 into main Jun 1, 2026
21 checks passed
@wmadden wmadden deleted the tml-2746-redesign-migration-graph-tier-3-condensed-annotated-node branch June 1, 2026 08:58
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