Skip to content

Commit ed30e6e

Browse files
sorenbsclaude
andauthored
Add a Prisma Next Migrations view
A Migrations item appears when the connected database carries a non-empty Prisma Next migration ledger: a newest-first timeline with change chips, a FigJam-style visual diff canvas built from the contract snapshots (morphing between migrations, all-models toggle, URL-addressable selection), and resizable SQL / Prisma-schema-diff panels with click-to-expand folds. Databases whose ledger predates contract snapshots degrade to the timeline + SQL with an update notice. Data model companion: prisma/prisma-next#908 (content-addressed prisma_contract.contract store joined onto the ledger's origin/destination hashes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 3631a93 commit ed30e6e

28 files changed

Lines changed: 52914 additions & 3 deletions

.changeset/migrations-view.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@prisma/studio-core": minor
3+
---
4+
5+
Add a Prisma Next Migrations view. When the connected database carries a Prisma Next migration ledger (`prisma_contract.ledger`) with at least one applied migration, Studio shows a Migrations navigation item with a newest-first timeline of every applied migration — name, apply time, operation count, destructive-change markers, and compact `+`/``/`~` chips summarizing what each migration changed.
6+
7+
Selecting a migration renders a visual, FigJam-style diff canvas built from the contract snapshots Prisma Next records alongside the ledger: added, removed, and changed models as color-coded cards with per-field before → after details (type, nullability, defaults, primary keys), enum cards, and relation edges. An All models toggle expands to the migration's full schema, switching migrations morphs the canvas over ~500ms instead of rebuilding it, and the selected migration is URL-addressable.
8+
9+
Collapsible detail panels behind a drag-resizable split show the executed SQL per operation and a Prisma-schema-style line diff of the before/after schema, with long unchanged runs collapsed into click-to-expand folds.
10+
11+
Databases whose ledger predates contract snapshots keep the timeline and SQL panel and show an update notice in place of the canvas. Requires a database migrated with a Prisma Next version that records contract snapshots for the visual and schema diffs.

Architecture/migrations-view.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Prisma Next Migrations View Architecture
2+
3+
This document is normative for the Studio Migrations view (`view=migrations`).
4+
5+
The view renders the Prisma Next migration history stored in the connected database. Studio does not read migration bundles from disk and does not talk to the Prisma Next CLI; the database ledger is the single source of truth.
6+
7+
## Scope
8+
9+
This architecture governs:
10+
11+
- detection of the Prisma Next migration ledger and Migrations navigation visibility
12+
- loading and normalizing `prisma_contract.ledger` rows
13+
- the contract snapshot diff engine
14+
- the visual diff canvas and migration timeline list
15+
- demo seeding of a migration history for `pnpm demo:ppg`
16+
17+
## Canonical Components
18+
19+
- [`ui/hooks/use-migrations.ts`](../ui/hooks/use-migrations.ts)
20+
- [`ui/studio/views/migrations/contract-diff.ts`](../ui/studio/views/migrations/contract-diff.ts)
21+
- [`ui/studio/views/migrations/diff-layout.ts`](../ui/studio/views/migrations/diff-layout.ts)
22+
- [`ui/studio/views/migrations/MigrationsView.tsx`](../ui/studio/views/migrations/MigrationsView.tsx)
23+
- [`ui/studio/Navigation.tsx`](../ui/studio/Navigation.tsx)
24+
- [`demo/ppg-dev/seed-migrations.ts`](../demo/ppg-dev/seed-migrations.ts)
25+
26+
## Data Source
27+
28+
Prisma Next records one row per applied migration in `prisma_contract.ledger`, and each distinct contract IR once in the content-addressed store `prisma_contract.contract` (`core_hash` PK — a contract's storage hash is its identity; see the Migration System subsystem doc in the prisma-next repository). The columns Studio consumes:
29+
30+
- `ledger.id` — apply order (bigserial)
31+
- `ledger.space` — contract space (`app` for the application schema)
32+
- `ledger.migration_name`, `ledger.migration_hash` — identity; the name is empty for synthesised `db init`/`db update` applies
33+
- `ledger.origin_core_hash`, `ledger.destination_core_hash` — the contract-graph edge; both are contract identifiers that resolve into the store by hash equality
34+
- `ledger.operations` — the executed operation envelopes, including per-step SQL and `operationClass`
35+
- `ledger.created_at` — apply time
36+
- `contract.contract_json` — full contract IR, LEFT JOINed twice (once per edge endpoint); null when no contract is stored under that hash
37+
38+
Both endpoint snapshots come straight from the joins — a baseline origin (no stored contract) yields a null before-state and diffs against the empty contract; an unresolved origin hash (out-of-band drift, snapshot-less predecessor) yields null rather than a wrong baseline. No client-side chain reconstruction exists. Rows are read through the standard `Adapter.raw` surface, so every executor (direct TCP, BFF, PGlite) works unchanged. Databases bootstrapped before the `contract` table existed are queried without the joins (detection below) — the list still renders, with empty diffs.
39+
40+
## Detection
41+
42+
`useMigrationsDetection` derives ledger and contract-table presence purely from introspection data (`introspection.schemas["prisma_contract"].tables["ledger"|"contract"]`); the contract-table flag picks the joined or join-less ledger query. The Migrations navigation item is gated by `useHasMigrationHistory`, which additionally runs a one-row `EXISTS` probe (not a full ledger fetch — snapshots can be megabytes of jsonb): a missing `prisma_contract` schema, a missing ledger table, or an empty ledger all hide the item. Stale `view=migrations` URLs against such a database show the view's empty state rather than breaking navigation.
43+
44+
## Missing Contract Data
45+
46+
When the ledger has rows but none of them joins to a contract snapshot (the `contract` table is missing or empty — a database written by a prisma-next predating the 1:1 table), there is nothing to diff: the view keeps the migration list and the SQL panel (both are pure ledger data), hides the All models toggle and Schema button, and replaces the canvas with an upgrade notice pointing at the latest Prisma Next. A single non-null snapshot anywhere renders the normal canvas with whatever data exists.
47+
48+
## Snapshot Coverage
49+
50+
Prisma Next writes only each apply's destination contract into the store, yet both endpoints of every edge resolve: every non-baseline origin hash was some predecessor apply's destination hash, so its contract is already stored, and content addressing means a contract revisited by a rollback cycle exists exactly once. The hash join is the correctness guard — there is nothing for Studio to verify or reconstruct client-side.
51+
52+
## Diff Engine
53+
54+
`contract-diff.ts` is pure and UI-free:
55+
56+
1. `parseContractSnapshot` normalizes a contract JSON document into flat models (fields with column, native type, nullability, rendered default, enum linkage, primary-key membership; relations; index/unique signatures) and enums. Malformed documents degrade to an empty snapshot instead of throwing.
57+
2. `diffContracts` classifies every model, field, enum, enum member, relation, and index signature as `added` / `removed` / `changed` / `unchanged`, with per-field change details (type, nullability, default) and aggregate stats.
58+
59+
Model-level structure comes from the contract's domain plane; storage-plane data (native types, defaults, primary keys, indexes) enriches it through each model's `storage.table` binding.
60+
61+
## Visual Canvas
62+
63+
The diff renders on a React Flow canvas (same dependency as the Schema Visualizer) with ELK auto-layout in `diff-layout.ts`. By default only touched models, their direct relation neighbors (rendered dimmed as context), and touched enums become nodes; the `All models` toggle (persisted UI state) expands to every model **and enum** in the migration's contract (unchanged ones in the dimmed context style), and a migration that touches nothing falls back to showing the full schema. Relation edges connect visible nodes, with added relations emphasized and removed relations drawn in the destructive color.
64+
65+
A model's status is table-anchored: only field or index changes mark it `changed`. Relation-only changes (a back-relation whose foreign key lives in the other table) keep the model `unchanged` — the added or removed relation surfaces through the emphasized edge, never through an amber card. This keeps the amber signal synonymous with "this table's DDL changed".
66+
67+
The canvas is a single persistent React Flow instance. Node ids are stable (`model:<name>` / `enum:<name>`), so switching migrations swaps node/edge arrays in place: surviving nodes glide to their new ELK positions via a CSS transform transition (see the `migrations-diff-canvas` rules in `ui/index.css`), entering nodes fade in, and the camera animates with `fitView({ duration: 500 })`. MUST NOT key the canvas or its wrapper by migration id — remounting is what causes the jarring full rebuild (and previously wedged exit animations).
68+
69+
The card styling is deliberately playful (FigJam-inspired): status-colored sticky-note cards with tape accents and deterministic tilt, `+`/``/`~` field glyphs, and before → after pills for changed field aspects. See `non-standard-ui.md` for the approved exception.
70+
71+
Selection state lives in the `migration` URL parameter (nuqs), so a specific migration diff is shareable.
72+
73+
The per-migration header (title, hash edge, diff-stat chips, view controls) floats over the top edge of the canvas as a translucent backdrop-blurred bar, so the canvas owns the full height of the content pane.
74+
75+
## Detail Panels
76+
77+
Two mutually exclusive collapsible panels sit under the canvas in a shared container whose height is user-resizable from a drag handle on its top edge (pointer drag plus ArrowUp/ArrowDown, clamped 120–640px, persisted UI state):
78+
79+
- **SQL** renders the ledger row's operation envelopes verbatim — labels, operation classes, and executed statements.
80+
- **Schema** renders a Prisma-schema-style diff. `psl-schema.ts` projects each contract snapshot into PSL-shaped text (model/enum blocks, mapped field types, defaults, relations, `@@index`/`@@unique`/`@@map`) and diffs the two texts line-by-line with the `diff` (jsdiff) package, collapsing long unchanged runs. The projection favors diff stability (fixed field ordering, no column alignment padding) over exact `prisma format` output. jsdiff was chosen over `@pierre/diffs` because the latter hard-depends on shiki, which is too heavy for the published bundle; the renderer is isolated in `MigrationSchemaPanel` so it can be swapped.
81+
82+
## Demo Seeding
83+
84+
`seed-migrations.ts` replays a fixture captured from the prisma-next `migrations-showcase` example (`demo/ppg-dev/fixtures/prisma-contract-migrations.json`): it recreates the `prisma_contract` schema (marker, ledger, and the hash-keyed contract store), upserts each migration's after-state under its destination hash (`ON CONFLICT DO NOTHING` — the fixture's `contract_json_before` values are intentionally not written; origins resolve as predecessors' destinations), restores the marker and ledger rows, and re-executes every operation's SQL in ledger order so the live schema matches the migration history exactly. Legacy demo volumes are upgraded in place: a `ledger_id`-keyed contract table is re-keyed by destination hash, and a two-column ledger has its after-states moved into the store and the bookend columns dropped. A few showcase rows are inserted so the resulting tables are browsable.

Architecture/non-standard-ui.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,19 @@ It deliberately excludes:
186186

187187
These are the current high-signal places where Studio is bypassing a plausible standard ShadCN component or composition pattern.
188188

189+
### Migration Diff Canvas Cards
190+
191+
- Canonical components:
192+
- [`ui/studio/views/migrations/MigrationsView.tsx`](ui/studio/views/migrations/MigrationsView.tsx)
193+
- [`ui/studio/views/migrations/diff-layout.ts`](ui/studio/views/migrations/diff-layout.ts)
194+
- Closest standard ShadCN alternative:
195+
- `Card` with `Badge`
196+
- Why it stays non-standard:
197+
- The Migrations view renders model/enum diff cards as React Flow custom nodes on a pan/zoom canvas with ELK layout; ShadCN `Card` cannot express canvas nodes with connection handles, status-driven color systems, or per-field diff glyph rows.
198+
- The FigJam-inspired treatment (status-colored sticky-note cards, tape accents, deterministic tilt, before → after change pills) is a deliberate product decision for this view so schema changes read as an approachable visual story rather than a table.
199+
- Required internals:
200+
- `Badge`, `Button`, `Skeleton` for the surrounding chrome; the timeline list items are custom buttons because they combine an index chip, name, timestamp, and diff-stat chips in one selectable row, which `Button` alone does not model.
201+
189202
### Introspection Status Notice
190203

191204
- Files:

FEATURES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@ The table and recommendation text label returned-row volume as `Rows Returned`;
155155
When Studio's shared `llm` hook is available, the query table adds an Analysis column that analyzes newly observed query groups in the background, one at a time, and stops automatic work after the first five groups. Rows show a running indicator, a manual Analyze action, and a completed all-good, info, or warning icon; the detail sheet uses the same analysis queue for manual recommendations. Without that hook, the AI analysis UI is hidden.
156156
If an embedder does not provide query insights, Studio hides the `Queries` menu item and stale `view=queries` URLs fall back to the normal default view.
157157

158+
## Prisma Next Migrations View
159+
160+
When the connected database carries a Prisma Next migration ledger (`prisma_contract.ledger`) with at least one applied migration, Studio shows a `Migrations` item in the main navigation; databases without the `prisma_contract` schema, without the ledger table, or with an empty ledger never see the entry.
161+
The view lists every applied migration newest-first — name, apply time, operation count, a destructive-change marker, and compact `+`/``/`~` chips summarizing what each migration did to models, fields, enums, and indexes.
162+
Selecting a migration renders a visual, UML-style diff on a pan/zoom canvas built from the contract snapshots Prisma Next records alongside the ledger (a content-addressed store joined onto each migration's origin/destination hashes): added models appear as green cards, removed models red with strikethrough, changed models amber with per-field detail rows (type, nullability, and default transitions rendered as before → after), plus enum cards and relation edges; untouched neighbor models render dimmed for context, and an `All models` toggle expands the canvas to the migration's full schema, including unchanged enums.
163+
A model only turns amber when its own table changed (fields or indexes); gaining a back-relation whose foreign key lives in the other table keeps the model dimmed while the new relation edge is emphasized instead.
164+
Switching between migrations morphs the canvas rather than rebuilding it: surviving model cards glide to their new layout positions and the camera refits over ~500ms, so stepping through adjacent migrations reads as one continuous story.
165+
Collapsible detail panels show the executed SQL (per-operation class and exact statements) and a Prisma-schema-style diff of the migration — a unified, color-coded line diff of the before/after schema with long unchanged runs collapsed into click-to-expand folds — and the split between canvas and panel is resizable from a drag handle. The migration header floats over the canvas top edge so the diagram gets the full content height. The selected migration is tracked in the URL so views can be shared and revisited.
166+
When the ledger has migrations but no contract snapshots to diff (a database written by an older Prisma Next), the list and SQL panel keep working and the canvas is replaced by a notice asking to update Prisma Next.
167+
158168
## Data Grid Browsing
159169

160170
Table data is shown in a grid with server-backed pagination, filtered-row counts, loading feedback, and explicit empty states.

0 commit comments

Comments
 (0)