@prisma-next/cli 0.12.0-dev.52 → 0.12.0-dev.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/cli.mjs +7 -7
  2. package/dist/{client-BfNYTr8w.mjs → client-DIcitJdy.mjs} +95 -38
  3. package/dist/client-DIcitJdy.mjs.map +1 -0
  4. package/dist/commands/contract-infer.mjs +1 -1
  5. package/dist/commands/db-init.mjs +2 -2
  6. package/dist/commands/db-schema.mjs +1 -1
  7. package/dist/commands/db-sign.mjs +1 -1
  8. package/dist/commands/db-update.mjs +2 -2
  9. package/dist/commands/db-verify.mjs +1 -1
  10. package/dist/commands/migrate.d.mts +35 -1
  11. package/dist/commands/migrate.d.mts.map +1 -1
  12. package/dist/commands/migrate.mjs +287 -6
  13. package/dist/commands/migrate.mjs.map +1 -1
  14. package/dist/commands/migration-check.mjs +2 -2
  15. package/dist/commands/migration-graph.d.mts.map +1 -1
  16. package/dist/commands/migration-graph.mjs +4 -2
  17. package/dist/commands/migration-graph.mjs.map +1 -1
  18. package/dist/commands/migration-list.d.mts +1 -0
  19. package/dist/commands/migration-list.d.mts.map +1 -1
  20. package/dist/commands/migration-list.mjs +1 -1
  21. package/dist/commands/migration-log.mjs +1 -1
  22. package/dist/commands/migration-show.mjs +2 -2
  23. package/dist/commands/migration-status.d.mts.map +1 -1
  24. package/dist/commands/migration-status.mjs +2 -2
  25. package/dist/{contract-infer-sUml0McE.mjs → contract-infer-BAdhYGQH.mjs} +2 -2
  26. package/dist/{contract-infer-sUml0McE.mjs.map → contract-infer-BAdhYGQH.mjs.map} +1 -1
  27. package/dist/{db-verify-DSZcaGQs.mjs → db-verify-CiUCDXnv.mjs} +2 -2
  28. package/dist/{db-verify-DSZcaGQs.mjs.map → db-verify-CiUCDXnv.mjs.map} +1 -1
  29. package/dist/exports/control-api.mjs +1 -1
  30. package/dist/{inspect-live-schema-B44OravN.mjs → inspect-live-schema-DegaqKFT.mjs} +2 -2
  31. package/dist/{inspect-live-schema-B44OravN.mjs.map → inspect-live-schema-DegaqKFT.mjs.map} +1 -1
  32. package/dist/{migration-check-BxWlQBOs.mjs → migration-check-ldvGvsgM.mjs} +2 -3
  33. package/dist/{migration-check-BxWlQBOs.mjs.map → migration-check-ldvGvsgM.mjs.map} +1 -1
  34. package/dist/{migration-command-scaffold-BvVTTjzK.mjs → migration-command-scaffold-D6UeN71F.mjs} +2 -2
  35. package/dist/{migration-command-scaffold-BvVTTjzK.mjs.map → migration-command-scaffold-D6UeN71F.mjs.map} +1 -1
  36. package/dist/{migration-graph-space-render-CeNXh_Wy.mjs → migration-graph-space-render-B0HkTNj3.mjs} +488 -84
  37. package/dist/migration-graph-space-render-B0HkTNj3.mjs.map +1 -0
  38. package/dist/{migration-list-vJWFuXca.mjs → migration-list-mYmj2j33.mjs} +6 -4
  39. package/dist/migration-list-mYmj2j33.mjs.map +1 -0
  40. package/dist/{migration-log-DD01Jm_i.mjs → migration-log-Dzs18GU7.mjs} +3 -3
  41. package/dist/{migration-log-DD01Jm_i.mjs.map → migration-log-Dzs18GU7.mjs.map} +1 -1
  42. package/dist/{migration-path-target-UkxkgXnv.mjs → migration-path-target-DK-B7POa.mjs} +1 -1
  43. package/dist/{migration-path-target-UkxkgXnv.mjs.map → migration-path-target-DK-B7POa.mjs.map} +1 -1
  44. package/dist/{migration-status-jL5ajRrB.mjs → migration-status-BGKGc0iR.mjs} +7 -5
  45. package/dist/migration-status-BGKGc0iR.mjs.map +1 -0
  46. package/dist/{schemas-DJY2O09F.mjs → schemas-B4xeMrNt.mjs} +1 -1
  47. package/dist/{schemas-DJY2O09F.mjs.map → schemas-B4xeMrNt.mjs.map} +1 -1
  48. package/package.json +18 -18
  49. package/src/commands/migrate.ts +512 -2
  50. package/src/commands/migration-graph.ts +2 -0
  51. package/src/commands/migration-list.ts +3 -0
  52. package/src/commands/migration-status.ts +4 -0
  53. package/src/control-api/operations/migrate.ts +149 -56
  54. package/src/utils/formatters/migration-graph-layout.ts +187 -42
  55. package/src/utils/formatters/migration-graph-space-render.ts +11 -1
  56. package/src/utils/formatters/migration-graph-tree-render.ts +609 -59
  57. package/src/utils/formatters/migration-list-render.ts +12 -0
  58. package/src/utils/formatters/migration-list-styler.ts +5 -7
  59. package/dist/client-BfNYTr8w.mjs.map +0 -1
  60. package/dist/migration-graph-space-render-CeNXh_Wy.mjs.map +0 -1
  61. package/dist/migration-list-vJWFuXca.mjs.map +0 -1
  62. package/dist/migration-status-jL5ajRrB.mjs.map +0 -1
@@ -143,9 +143,14 @@ function refineAdjacency(rows, nodeColumn, position, forwardInDegree, forwardOut
143
143
  if (row.kind !== "edge" || row.edge === void 0 || row.laneIndex === void 0) return row;
144
144
  const divergenceBranchEdge = row.edge.kind === "forward" && !(row.convergenceProducer ?? false) && (forwardOutDegree.get(row.edge.from) ?? 0) >= 2 && branchLaneForEdge(row.edge) !== void 0;
145
145
  const adjacency = row.convergenceProducer === true && convergenceProducerUsesShortAdjacency(row.edge, row.laneIndex, forwardProducersByTo, producerLaneByHash) ? classifyForwardShortConvergenceAdjacency(rows, rowIndex, row.edge, row.laneIndex) : classifyLayoutAdjacency(rows, rowIndex, row.edge, row.laneIndex, row.passThroughLanes ?? [], nodeColumn, position, forwardInDegree, row.convergenceProducer ?? false, divergenceBranchEdge);
146
+ const existingLaneEdge = /* @__PURE__ */ new Map();
147
+ for (const lane of row.passThroughLanes ?? []) {
148
+ const cell = row.cells[lane];
149
+ if (cell !== void 0 && "migrationHash" in cell && cell.migrationHash !== void 0) existingLaneEdge.set(lane, cell.migrationHash);
150
+ }
146
151
  return {
147
152
  ...row,
148
- cells: buildEdgeCells(row.edge, row.laneIndex, row.passThroughLanes ?? [], adjacency, row.cells.length)
153
+ cells: buildEdgeCells(row.edge, row.laneIndex, row.passThroughLanes ?? [], adjacency, row.cells.length, existingLaneEdge)
149
154
  };
150
155
  });
151
156
  }
@@ -164,48 +169,100 @@ function classifyEdgeAdjacency(edge, position) {
164
169
  function emptyCells(width) {
165
170
  return Array.from({ length: width }, () => ({ kind: "empty" }));
166
171
  }
167
- function buildBranchConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth) {
172
+ /** Returns `{ migrationHash: hash }` when hash is defined, otherwise `{}`. */
173
+ function hashProp(hash) {
174
+ return hash !== void 0 ? { migrationHash: hash } : {};
175
+ }
176
+ /** Returns `{ arcMigrationHash: hash }` when hash is defined, otherwise `{}`. */
177
+ function arcHashProp(hash) {
178
+ return hash !== void 0 ? { arcMigrationHash: hash } : {};
179
+ }
180
+ function buildBranchConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth, trunkEdgeHash, fanEdgeHashByLane, laneEdgeByIndex) {
168
181
  const cells = emptyCells(gridWidth);
169
182
  for (let lane = 0; lane < gridWidth; lane++) {
170
183
  if (activeLanes.has(lane) && (lane < startLane || lane > endLane)) {
171
- cells[lane] = { kind: "vertical-pass" };
184
+ cells[lane] = {
185
+ kind: "vertical-pass",
186
+ ...hashProp(laneEdgeByIndex.get(lane))
187
+ };
172
188
  continue;
173
189
  }
174
- if (lane === startLane) cells[lane] = { kind: "branch-tee" };
175
- else if (lane === endLane) cells[lane] = { kind: "branch-corner" };
176
- else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = { kind: "branch-tee" };
177
- else if (activeLanes.has(lane)) cells[lane] = { kind: "arc-crossing" };
178
- else cells[lane] = { kind: "branch-tee" };
190
+ if (lane === startLane) cells[lane] = {
191
+ kind: "branch-tee",
192
+ ...hashProp(trunkEdgeHash)
193
+ };
194
+ else if (lane === endLane) cells[lane] = {
195
+ kind: "branch-corner",
196
+ ...hashProp(fanEdgeHashByLane.get(lane))
197
+ };
198
+ else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = {
199
+ kind: "branch-tee",
200
+ ...hashProp(fanEdgeHashByLane.get(lane))
201
+ };
202
+ else if (activeLanes.has(lane)) cells[lane] = {
203
+ kind: "arc-crossing",
204
+ ...hashProp(laneEdgeByIndex.get(lane)),
205
+ ...arcHashProp(fanEdgeHashByLane.get(endLane))
206
+ };
207
+ else cells[lane] = {
208
+ kind: "branch-tee",
209
+ ...hashProp(fanEdgeHashByLane.get(lane))
210
+ };
179
211
  }
180
212
  return cells;
181
213
  }
182
- function buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth) {
214
+ function buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth, laneEdgeByIndex) {
183
215
  const cells = emptyCells(gridWidth);
184
216
  for (let lane = 0; lane < gridWidth; lane++) {
185
217
  if (activeLanes.has(lane) && (lane < startLane || lane > endLane)) {
186
- cells[lane] = { kind: "vertical-pass" };
218
+ cells[lane] = {
219
+ kind: "vertical-pass",
220
+ ...hashProp(laneEdgeByIndex.get(lane))
221
+ };
187
222
  continue;
188
223
  }
189
- if (lane === startLane) cells[lane] = { kind: "merge-tee" };
190
- else if (lane === endLane) cells[lane] = { kind: "merge-corner" };
191
- else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = { kind: "merge-tee" };
192
- else if (activeLanes.has(lane)) cells[lane] = { kind: "arc-crossing" };
193
- else cells[lane] = { kind: "horizontal-pass" };
224
+ if (lane === startLane) cells[lane] = {
225
+ kind: "merge-tee",
226
+ ...hashProp(laneEdgeByIndex.get(lane))
227
+ };
228
+ else if (lane === endLane) cells[lane] = {
229
+ kind: "merge-corner",
230
+ ...hashProp(laneEdgeByIndex.get(lane))
231
+ };
232
+ else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = {
233
+ kind: "merge-tee",
234
+ ...hashProp(laneEdgeByIndex.get(lane))
235
+ };
236
+ else if (activeLanes.has(lane)) cells[lane] = {
237
+ kind: "arc-crossing",
238
+ ...hashProp(laneEdgeByIndex.get(lane)),
239
+ ...arcHashProp(laneEdgeByIndex.get(endLane))
240
+ };
241
+ else cells[lane] = {
242
+ kind: "horizontal-pass",
243
+ ...hashProp(laneEdgeByIndex.get(startLane))
244
+ };
194
245
  }
195
246
  return cells;
196
247
  }
197
- function buildNodeCells(contractHash, nodeColumn, activeLanes, gridWidth) {
248
+ function buildNodeCells(contractHash, nodeColumn, activeLanes, gridWidth, laneEdgeByIndex) {
198
249
  const cells = emptyCells(gridWidth);
199
- for (const lane of activeLanes) if (lane !== nodeColumn && lane < gridWidth) cells[lane] = { kind: "vertical-pass" };
250
+ for (const lane of activeLanes) if (lane !== nodeColumn && lane < gridWidth) cells[lane] = {
251
+ kind: "vertical-pass",
252
+ ...hashProp(laneEdgeByIndex.get(lane))
253
+ };
200
254
  if (nodeColumn < gridWidth) cells[nodeColumn] = {
201
255
  kind: "node",
202
256
  contractHash
203
257
  };
204
258
  return cells;
205
259
  }
206
- function buildEdgeCells(edge, laneIndex, passThroughLanes, adjacency, gridWidth) {
260
+ function buildEdgeCells(edge, laneIndex, passThroughLanes, adjacency, gridWidth, laneEdgeByIndex) {
207
261
  const cells = emptyCells(gridWidth);
208
- for (const lane of passThroughLanes) if (lane < gridWidth) cells[lane] = { kind: "vertical-pass" };
262
+ for (const lane of passThroughLanes) if (lane < gridWidth) cells[lane] = {
263
+ kind: "vertical-pass",
264
+ ...hashProp(laneEdgeByIndex.get(lane))
265
+ };
209
266
  if (laneIndex < gridWidth) cells[laneIndex] = {
210
267
  kind: "edge-lane",
211
268
  migrationHash: edge.migrationHash,
@@ -357,6 +414,7 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
357
414
  const maxCoSourcedLane = Math.max(...coSourcedLanes);
358
415
  const coLandingLanes = routes.filter((other) => other.edge.to === edge.to).map((other) => other.backLane);
359
416
  const maxCoLandingLane = Math.max(...coLandingLanes);
417
+ const { migrationHash: arcHash } = edge;
360
418
  const sourceRow = result[sourceRowIndex];
361
419
  if (sourceRow !== void 0) {
362
420
  const cells = sourceRow.cells;
@@ -368,22 +426,43 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
368
426
  };
369
427
  for (let lane = nodeCol + 1; lane < backLane; lane += 1) {
370
428
  if (coSourcedLanes.includes(lane)) {
371
- cells[lane] = { kind: "arc-branch-tee" };
429
+ cells[lane] = {
430
+ kind: "arc-branch-tee",
431
+ ...hashProp(routes.find((r) => r.backLane === lane && r.edge.from === edge.from)?.edge.migrationHash)
432
+ };
372
433
  continue;
373
434
  }
374
435
  const existing = cells[lane];
375
- cells[lane] = existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" || routes.some((other) => other.edge.migrationHash !== edge.migrationHash && other.backLane === lane && routeCrossesRow(other, sourceRowIndex, result)) ? { kind: "arc-crossing" } : { kind: "horizontal-pass" };
436
+ if (existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" || routes.some((other) => other.edge.migrationHash !== arcHash && other.backLane === lane && routeCrossesRow(other, sourceRowIndex, result))) cells[lane] = {
437
+ kind: "arc-crossing",
438
+ ...hashProp(existing !== void 0 && "migrationHash" in existing ? existing.migrationHash : void 0),
439
+ arcMigrationHash: arcHash
440
+ };
441
+ else cells[lane] = {
442
+ kind: "horizontal-pass",
443
+ migrationHash: arcHash
444
+ };
376
445
  }
377
- cells[backLane] = backLane < maxCoSourcedLane ? { kind: "arc-branch-tee" } : { kind: "arc-branch-corner" };
446
+ cells[backLane] = backLane < maxCoSourcedLane ? {
447
+ kind: "arc-branch-tee",
448
+ migrationHash: arcHash
449
+ } : {
450
+ kind: "arc-branch-corner",
451
+ migrationHash: arcHash
452
+ };
378
453
  }
379
454
  const edgeRow = result[edgeRowIndex];
380
455
  if (edgeRow !== void 0) {
381
456
  const cells = edgeRow.cells;
382
457
  ensureCellWidth(cells, backLane + 1);
383
- cells[nodeCol] = { kind: "vertical-pass" };
458
+ const forwardLaneCell = cells[nodeCol];
459
+ cells[nodeCol] = {
460
+ kind: "vertical-pass",
461
+ ...hashProp(forwardLaneCell !== void 0 && "migrationHash" in forwardLaneCell ? forwardLaneCell.migrationHash : void 0)
462
+ };
384
463
  cells[backLane] = {
385
464
  kind: "edge-lane",
386
- migrationHash: edge.migrationHash,
465
+ migrationHash: arcHash,
387
466
  edgeKind: edge.kind,
388
467
  ownsLabel: true,
389
468
  adjacency: "node-skipping-rollback"
@@ -401,7 +480,10 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
401
480
  const cells = row.cells;
402
481
  ensureCellWidth(cells, backLane + 1);
403
482
  const existing = cells[backLane];
404
- if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-tee" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "arc-branch-corner" && existing?.kind !== "arc-branch-tee" && existing?.kind !== "arc-crossing") cells[backLane] = { kind: "vertical-pass" };
483
+ if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-tee" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "arc-branch-corner" && existing?.kind !== "arc-branch-tee" && existing?.kind !== "arc-crossing") cells[backLane] = {
484
+ kind: "vertical-pass",
485
+ migrationHash: arcHash
486
+ };
405
487
  }
406
488
  const targetRow = result[targetRowIndex];
407
489
  if (targetRow !== void 0) {
@@ -414,19 +496,39 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
414
496
  };
415
497
  for (let lane = targetCol + 1; lane < backLane; lane += 1) {
416
498
  if (coLandingLanes.includes(lane)) {
417
- cells[lane] = { kind: "arc-land-tee" };
499
+ cells[lane] = {
500
+ kind: "arc-land-tee",
501
+ ...hashProp(routes.find((r) => r.backLane === lane && r.edge.to === edge.to)?.edge.migrationHash)
502
+ };
418
503
  continue;
419
504
  }
420
505
  const existing = cells[lane];
421
- cells[lane] = existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" && existing.kind !== "arc-land-tee" || routes.some((other) => other.edge.migrationHash !== edge.migrationHash && other.backLane === lane && routeCrossesRow(other, targetRowIndex, result)) ? { kind: "arc-crossing" } : { kind: "arc-land-bridge" };
506
+ if (existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" && existing.kind !== "arc-land-tee" || routes.some((other) => other.edge.migrationHash !== arcHash && other.backLane === lane && routeCrossesRow(other, targetRowIndex, result))) cells[lane] = {
507
+ kind: "arc-crossing",
508
+ ...hashProp(existing !== void 0 && "migrationHash" in existing ? existing.migrationHash : void 0),
509
+ arcMigrationHash: arcHash
510
+ };
511
+ else cells[lane] = {
512
+ kind: "arc-land-bridge",
513
+ migrationHash: arcHash
514
+ };
422
515
  }
423
- cells[backLane] = backLane < maxCoLandingLane ? { kind: "arc-land-tee" } : { kind: "arc-land-corner" };
516
+ cells[backLane] = backLane < maxCoLandingLane ? {
517
+ kind: "arc-land-tee",
518
+ migrationHash: arcHash
519
+ } : {
520
+ kind: "arc-land-corner",
521
+ migrationHash: arcHash
522
+ };
424
523
  for (const other of routes) {
425
524
  if (other.backLane <= backLane) continue;
426
525
  if (!routeCrossesRow(other, targetRowIndex, result)) continue;
427
526
  ensureCellWidth(cells, other.backLane + 1);
428
527
  const existing = cells[other.backLane];
429
- if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-tee" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "node") cells[other.backLane] = { kind: "vertical-pass" };
528
+ if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-tee" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "node") cells[other.backLane] = {
529
+ kind: "vertical-pass",
530
+ migrationHash: other.edge.migrationHash
531
+ };
430
532
  }
431
533
  }
432
534
  }
@@ -476,6 +578,7 @@ function layoutComponent(componentNodes, allEdges) {
476
578
  const nodeColumn = /* @__PURE__ */ new Map();
477
579
  const edgeColumn = /* @__PURE__ */ new Map();
478
580
  const producerLaneByHash = /* @__PURE__ */ new Map();
581
+ const laneEdgeByIndex = /* @__PURE__ */ new Map();
479
582
  let gridWidth = 1;
480
583
  function ensureGridWidth(minWidth) {
481
584
  if (minWidth > gridWidth) gridWidth = minWidth;
@@ -514,21 +617,22 @@ function layoutComponent(componentNodes, allEdges) {
514
617
  startLane,
515
618
  endLane,
516
619
  branchCount: laneIndices.length,
517
- cells: buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth)
620
+ cells: buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth, laneEdgeByIndex)
518
621
  });
519
622
  for (const index of laneIndices) if (index !== startLane) setLane(index, null);
520
623
  return startLane;
521
624
  }
522
- function emitBranchConnector(contractHash, startLane, endLane, branchCount, fanTargetLanes) {
625
+ function emitBranchConnector(contractHash, startLane, endLane, branchCount, fanTargetLanes, fanEdgeHashByLane) {
523
626
  ensureGridWidth(endLane + 1);
524
627
  const activeLanes = new Set(activeLaneIndices());
628
+ const trunkEdgeHash = fanEdgeHashByLane.get(startLane) ?? laneEdgeByIndex.get(startLane);
525
629
  rows.push({
526
630
  kind: "branch-connector",
527
631
  contractHash,
528
632
  startLane,
529
633
  endLane,
530
634
  branchCount,
531
- cells: buildBranchConnectorCells(startLane, endLane, new Set(fanTargetLanes), activeLanes, gridWidth)
635
+ cells: buildBranchConnectorCells(startLane, endLane, new Set(fanTargetLanes), activeLanes, gridWidth, trunkEdgeHash, fanEdgeHashByLane, laneEdgeByIndex)
532
636
  });
533
637
  }
534
638
  function emitEdgeRow(edge, lane, convergenceProducer) {
@@ -540,7 +644,7 @@ function layoutComponent(componentNodes, allEdges) {
540
644
  edge,
541
645
  laneIndex: lane,
542
646
  passThroughLanes: passThrough,
543
- cells: buildEdgeCells(edge, lane, passThrough, adjacency, gridWidth)
647
+ cells: buildEdgeCells(edge, lane, passThrough, adjacency, gridWidth, laneEdgeByIndex)
544
648
  };
545
649
  rows.push(convergenceProducer ? {
546
650
  ...row,
@@ -548,6 +652,7 @@ function layoutComponent(componentNodes, allEdges) {
548
652
  } : row);
549
653
  edgeColumn.set(edge.migrationHash, lane);
550
654
  if (convergenceProducer) producerLaneByHash.set(edge.migrationHash, lane);
655
+ laneEdgeByIndex.set(lane, edge.migrationHash);
551
656
  }
552
657
  function emitNodeRow(contractHash, column) {
553
658
  ensureGridWidth(column + 1);
@@ -555,7 +660,7 @@ function layoutComponent(componentNodes, allEdges) {
555
660
  rows.push({
556
661
  kind: "node",
557
662
  contractHash,
558
- cells: buildNodeCells(contractHash, column, passThrough, gridWidth)
663
+ cells: buildNodeCells(contractHash, column, passThrough, gridWidth, laneEdgeByIndex)
559
664
  });
560
665
  nodeColumn.set(contractHash, column);
561
666
  }
@@ -601,7 +706,21 @@ function layoutComponent(componentNodes, allEdges) {
601
706
  }
602
707
  if (groups.length >= 2) {
603
708
  const endLane = Math.max(...laneForGroup);
604
- emitBranchConnector(node, column, endLane, groups.length, laneForGroup);
709
+ const fanEdgeHashByLane = /* @__PURE__ */ new Map();
710
+ for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
711
+ const group = groups[groupIndex];
712
+ const lane = laneForGroup[groupIndex];
713
+ if (group === void 0 || lane === void 0) continue;
714
+ const firstEdge = group.edges[0];
715
+ if (firstEdge !== void 0) fanEdgeHashByLane.set(lane, firstEdge.migrationHash);
716
+ }
717
+ emitBranchConnector(node, column, endLane, groups.length, laneForGroup, fanEdgeHashByLane);
718
+ for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
719
+ const fanLane = laneForGroup[groupIndex];
720
+ if (fanLane === void 0) continue;
721
+ const fanHash = fanEdgeHashByLane.get(fanLane);
722
+ if (fanHash !== void 0) laneEdgeByIndex.set(fanLane, fanHash);
723
+ }
605
724
  }
606
725
  for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
607
726
  const group = groups[groupIndex];
@@ -1349,20 +1468,23 @@ function buildRefsByHashFromListEntries(entries) {
1349
1468
  function formatEmptyStateLine(spaceId, style) {
1350
1469
  return style.emptyState(`There are no migrations in migrations/${spaceId}/ yet`);
1351
1470
  }
1352
- function renderSpaceTreeBlock(spaceId, migrations, multiSpace, glyphMode, style, colorize, liveContractHash, graphForSpace, globalMaxEdgeTreePrefixWidth, globalMaxDirNameWidth) {
1471
+ function renderSpaceTreeBlock(spaceId, migrations, multiSpace, glyphMode, style, colorize, liveContractHash, graphForSpace, appSpaceId, globalMaxEdgeTreePrefixWidth, globalMaxDirNameWidth) {
1353
1472
  if (migrations.length === 0) {
1354
1473
  const emptyLine = formatEmptyStateLine(spaceId, style);
1355
1474
  if (!multiSpace) return [emptyLine];
1356
1475
  return [style.spaceHeading(`${spaceId}:`), ` ${emptyLine}`];
1357
1476
  }
1477
+ const graph = graphForSpace(spaceId) ?? migrationGraphFromListEntries(migrations);
1478
+ const isAppSpace = appSpaceId === void 0 ? void 0 : spaceId === appSpaceId;
1358
1479
  const treeOutput = renderMigrationGraphSpaceTree({
1359
- graph: graphForSpace(spaceId) ?? migrationGraphFromListEntries(migrations),
1480
+ graph,
1360
1481
  migrations,
1361
1482
  liveContractHash,
1362
1483
  glyphMode,
1363
1484
  colorize,
1364
1485
  refsByHash: buildRefsByHashFromListEntries(migrations),
1365
1486
  styler: style,
1487
+ ...isAppSpace !== void 0 ? { isAppSpace } : {},
1366
1488
  ...globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth } : {},
1367
1489
  ...globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth } : {}
1368
1490
  });
@@ -1383,6 +1505,7 @@ function renderMigrationListWithStyle(result, style, glyphMode = "unicode", opti
1383
1505
  const colorize = options.colorize ?? false;
1384
1506
  const liveContractHash = options.liveContractHash ?? EMPTY_CONTRACT_HASH;
1385
1507
  const graphForSpace = options.graphForSpace ?? (() => void 0);
1508
+ const appSpaceId = options.appSpaceId;
1386
1509
  const globalLayoutInputs = multiSpace ? result.spaces.filter((space) => space.migrations.length > 0).map((space) => ({
1387
1510
  graph: graphForSpace(space.space) ?? migrationGraphFromListEntries(space.migrations),
1388
1511
  liveContractHash
@@ -1393,7 +1516,7 @@ function renderMigrationListWithStyle(result, style, glyphMode = "unicode", opti
1393
1516
  for (let index = 0; index < result.spaces.length; index++) {
1394
1517
  const space = result.spaces[index];
1395
1518
  if (index > 0) lines.push("");
1396
- lines.push(...renderSpaceTreeBlock(space.space, space.migrations, multiSpace, glyphMode, style, colorize, liveContractHash, graphForSpace, globalMaxEdgeTreePrefixWidth, globalMaxDirNameWidth));
1519
+ lines.push(...renderSpaceTreeBlock(space.space, space.migrations, multiSpace, glyphMode, style, colorize, liveContractHash, graphForSpace, appSpaceId, globalMaxEdgeTreePrefixWidth, globalMaxDirNameWidth));
1397
1520
  }
1398
1521
  if (result.spaces.reduce((count, space) => count + space.migrations.length, 0) > 0) {
1399
1522
  lines.push("");
@@ -1410,7 +1533,7 @@ function styleMarkerName(name) {
1410
1533
  return name === "contract" ? bold(green(name)) : green(name);
1411
1534
  }
1412
1535
  function plainMarkers(names) {
1413
- return `<${names.join(", ")}>`;
1536
+ return names.map((name) => `@${name}`).join(" ");
1414
1537
  }
1415
1538
  function formatContractNodeOverlays(styler, markers, refs) {
1416
1539
  const parts = [];
@@ -1444,8 +1567,8 @@ function styleRefName(name) {
1444
1567
  * - `glyph` (`→` / `⟲` / `∅`): dim
1445
1568
  * - `lane` (graph gutter lines `│` and fan/join connectors `├─┐` / `├─┘`): dim
1446
1569
  * - `invariants` (`{...}`): yellow
1447
- * - `markers` (`<...>`): green; the `contract` desired-state marker inside is
1448
- * green-bold (`db` is plain green)
1570
+ * - `markers` (`@contract @db`): green; the `contract` desired-state marker is
1571
+ * green-bold (`db` is plain green); the `@` sigil is applied to each name
1449
1572
  * - `refs` (`(...)`): green (the active ref is bolded separately by the tree styler)
1450
1573
  * - `spaceHeading` (`<spaceId>:`): bold
1451
1574
  * - `summary`: dim
@@ -1465,10 +1588,8 @@ function createAnsiMigrationListStyler(opts) {
1465
1588
  lane: (text) => dim(text),
1466
1589
  invariants: (ids) => yellow(`{${ids.join(", ")}}`),
1467
1590
  markers: (names) => {
1468
- const open = green("<");
1469
- const close = green(">");
1470
- const separator = green(", ");
1471
- return open + names.map(styleMarkerName).join(separator) + close;
1591
+ const sigil = green("@");
1592
+ return names.map((name) => sigil + styleMarkerName(name)).join(" ");
1472
1593
  },
1473
1594
  refs: (names) => {
1474
1595
  const open = green("(");
@@ -1560,12 +1681,71 @@ function arrowForEdgeKind(kind, palette) {
1560
1681
  return palette.edgeArrow[kind];
1561
1682
  }
1562
1683
  /**
1563
- * Forced bold for branch-coloured names. A branched name pairs its lane hue
1564
- * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
1565
- * when colorette's ambient TTY detection is off, so the colorized branch name
1566
- * is deterministically bold + hue rather than hue-only.
1684
+ * Forced-color functions that always emit ANSI regardless of the ambient TTY
1685
+ * environment (NO_COLOR, piped output). Used for:
1686
+ *
1687
+ * - `forcedBold`: branch-coloured migration names pair their lane hue with bold;
1688
+ * both must emit so the name is deterministically bold + hue.
1689
+ * - `forcedDim`: off-path path-highlight override (migrate --show).
1690
+ * The renderer gates this behind `opts.colorize`; the forced variant ensures
1691
+ * ANSI is emitted in controlled environments (e.g. tests with `NO_COLOR=1`)
1692
+ * when the caller explicitly requests colour. Without forcing, `dim()` from
1693
+ * the ambient module-level import no-ops under NO_COLOR, making the
1694
+ * path-highlight unreachable in tests.
1567
1695
  */
1568
- const { bold: forcedBold } = createColors({ useColor: true });
1696
+ const { bold: forcedBold, dim: forcedDim, greenBright: forcedGreen } = createColors({ useColor: true });
1697
+ /**
1698
+ * The two styles used in `migrate --show` path-highlight mode.
1699
+ *
1700
+ * In path-highlight mode the normal by-branch rotating-colour logic
1701
+ * (`LANE_COLOR_CYCLE` / `laneStylerForColumn`) is suppressed entirely.
1702
+ * Every glyph, name, and hash is styled by its on-path / off-path role,
1703
+ * never by lane column index.
1704
+ *
1705
+ * - `onPath`: neutral single-path style — exactly how a linear (no-branch)
1706
+ * section renders today. Lane glyphs are dim, names are bold, hashes use
1707
+ * the default `sourceHash`/`destHash` colours. No rotation hue is applied.
1708
+ * This is identical to how the pgvector single-path section renders.
1709
+ * - `offPath`: uniform dim grey on every cell (name, hashes, lane glyphs,
1710
+ * direction arrows).
1711
+ *
1712
+ * To change the on-path or off-path colour in future, edit this object only.
1713
+ */
1714
+ const PATH_HIGHLIGHT_STYLES = {
1715
+ /**
1716
+ * Lane/glyph/arrow stylers for on-path cells.
1717
+ *
1718
+ * - lane: `forcedGreen` when colour is on — bright green so the on-path
1719
+ * branch glyphs (`│ ├ ╯ ↑`) and node markers (`○`/`∅`) are visually
1720
+ * distinct from off-path (dim grey). Uses forced ANSI so it survives
1721
+ * NO_COLOR in tests. Identity when `colorize` is false.
1722
+ * - arrow: identity (plain, no colouring)
1723
+ * - dirName: `bold` (ambient bold — name stays white/bold, not green)
1724
+ * - hashOverride: undefined — `style.sourceHash`/`style.destHash` apply
1725
+ * normally (cyan) so hashes keep their existing neutral colour.
1726
+ *
1727
+ * `style` is the same `MigrationListStyler` the tree renderer uses.
1728
+ * Rotation (`LANE_COLOR_CYCLE`) is never applied to on-path cells.
1729
+ */
1730
+ onPath: (_style, colorize) => ({
1731
+ lane: colorize ? forcedGreen : (text) => text,
1732
+ arrow: (text) => text,
1733
+ dirName: (text) => bold(text),
1734
+ hashOverride: void 0
1735
+ }),
1736
+ /**
1737
+ * Lane/glyph/arrow/hash stylers for off-path cells.
1738
+ * Uniform dim grey on everything — uses `forcedDim` so ANSI is emitted even
1739
+ * under NO_COLOR (test environments use `colorize:true` + NO_COLOR=1 to verify dim).
1740
+ * Returns identity functions when colour is off (`colorize: false`).
1741
+ */
1742
+ offPath: (colorize) => ({
1743
+ lane: colorize ? forcedDim : (text) => text,
1744
+ arrow: colorize ? forcedDim : (text) => text,
1745
+ dirName: colorize ? forcedDim : (text) => text,
1746
+ hashOverride: colorize ? forcedDim : void 0
1747
+ })
1748
+ };
1569
1749
  function laneStylerForColumn(colorColumn, colorize, style) {
1570
1750
  return stylerForLaneColumn(colorColumn, colorize, style.lane);
1571
1751
  }
@@ -1605,24 +1785,30 @@ function renderConnectorTee(pair, glyphColumn, dashColumn, colorize, style) {
1605
1785
  * marker takes its own lane's hue (so each node visibly belongs to its branch);
1606
1786
  * the connector follows the arc it belongs to (its owning back-lane hue).
1607
1787
  * Direction arrows are handled elsewhere — they take their edge's lane hue too.
1788
+ *
1789
+ * When `laneOverride` is provided (for path-highlight rows), it replaces the
1790
+ * marker styler. `arcLaneOverride` (if provided) replaces the connector styler
1791
+ * independently — this matters when the node is on-path but the arc belongs to
1792
+ * an off-path rollback edge, which must render dim rather than green.
1608
1793
  */
1609
- function renderNodeMarkerPair(pair, nodeColumn, arcColumn, colorize, style) {
1610
- const marker = laneStylerForColumn(nodeColumn, colorize, style);
1611
- const connector = laneStylerForColumn(arcColumn, colorize, style);
1794
+ function renderNodeMarkerPair(pair, nodeColumn, arcColumn, colorize, style, laneOverride, arcLaneOverride) {
1795
+ const marker = laneOverride ?? laneStylerForColumn(nodeColumn, colorize, style);
1796
+ const connector = arcLaneOverride ?? laneOverride ?? laneStylerForColumn(arcColumn, colorize, style);
1612
1797
  return marker(pair.slice(0, 1)) + connector(pair.slice(1));
1613
1798
  }
1614
- function renderCellPair(cell, column, colors, colorize, style, palette) {
1799
+ function renderCellPair(cell, column, colors, colorize, style, palette, laneOverride, arrowOverride, arcLaneOverride) {
1615
1800
  const laneColumn = colors.lane[column] ?? column;
1616
- const lane = laneStylerForColumn(laneColumn, colorize, style);
1801
+ const lane = laneOverride ?? laneStylerForColumn(laneColumn, colorize, style);
1802
+ const arrow = arrowOverride ?? ((text) => branchStylerOrDefault(column, colorize, style.kind)(text));
1617
1803
  switch (cell.kind) {
1618
1804
  case "node": {
1619
1805
  const arcColumn = colors.connector[column] ?? 0;
1620
- if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
1621
- if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
1806
+ if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style, laneOverride, arcLaneOverride);
1807
+ if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style, laneOverride, arcLaneOverride);
1622
1808
  return lane(palette.node);
1623
1809
  }
1624
1810
  case "vertical-pass": return lane(palette.verticalPass);
1625
- case "edge-lane": return cell.ownsLabel ? lane(palette.verticalPass.trimEnd()) + branchStylerOrDefault(column, colorize, style.kind)(arrowForEdgeKind(cell.edgeKind, palette)) : lane(palette.verticalPass);
1811
+ case "edge-lane": return cell.ownsLabel ? lane(palette.verticalPass.trimEnd()) + arrow(arrowForEdgeKind(cell.edgeKind, palette)) : lane(palette.verticalPass);
1626
1812
  case "branch-tee": return lane(palette.branchTee);
1627
1813
  case "merge-tee": return lane(palette.mergeTee);
1628
1814
  case "branch-corner": return lane(palette.branchCorner);
@@ -1630,14 +1816,26 @@ function renderCellPair(cell, column, colors, colorize, style, palette) {
1630
1816
  case "arc-branch-corner": return lane(palette.arcBranchCorner);
1631
1817
  case "arc-branch-tee": return lane(palette.arcBranchTee);
1632
1818
  case "arc-land-corner": return lane(palette.arcLandCorner);
1633
- case "arc-land-tee": return renderConnectorTee(palette.arcLandTee, laneColumn, colors.dash[column] ?? laneColumn, colorize, style);
1819
+ case "arc-land-tee": return laneOverride !== void 0 ? laneOverride(palette.arcLandTee) : renderConnectorTee(palette.arcLandTee, laneColumn, colors.dash[column] ?? laneColumn, colorize, style);
1634
1820
  case "arc-crossing": return lane(palette.arcLandBridge);
1635
1821
  case "arc-land-bridge": return lane(palette.arcLandBridge);
1636
1822
  case "horizontal-pass": return lane(palette.horizontalPass);
1637
1823
  case "empty": return " ";
1638
1824
  }
1639
1825
  }
1640
- function renderConnectorRow(row, gridWidth, colorize, style, palette) {
1826
+ /**
1827
+ * Render a branch-connector or merge-connector row.
1828
+ *
1829
+ * `columnLaneOverride` is an optional per-column map populated when path-highlight
1830
+ * annotations are active (`migrate --show`). For each column in the connector's
1831
+ * lane range, the map supplies the override styler (dim for off-path) that should
1832
+ * replace the normal rotating-lane colour for that column. Columns absent from the
1833
+ * map (on-path or unannotated) use the standard `laneStylerForColumn` logic unchanged.
1834
+ * This ensures off-path branch connectors appear dim rather than in their rotation
1835
+ * colour (e.g. magenta).
1836
+ */
1837
+ function renderConnectorRow(row, gridWidth, colorize, style, palette, columnLaneOverride) {
1838
+ const resolvedLane = (column) => columnLaneOverride?.get(column) ?? laneStylerForColumn(column, colorize, style);
1641
1839
  const isMerge = row.kind === "merge-connector";
1642
1840
  if (row.cells.length > 0) {
1643
1841
  const colors = resolveConnectorLaneColors(row.cells, row.startLane ?? 0);
@@ -1648,6 +1846,43 @@ function renderConnectorRow(row, gridWidth, colorize, style, palette) {
1648
1846
  if (cell === void 0) continue;
1649
1847
  const glyphColumn = colors.glyph[column] ?? column;
1650
1848
  const dashColumn = colors.dash[column] ?? glyphColumn;
1849
+ const override = columnLaneOverride?.get(glyphColumn);
1850
+ const dashOverrideForPathHighlight = columnLaneOverride?.get(dashColumn) ?? override;
1851
+ if (override !== void 0 || columnLaneOverride !== void 0 && dashOverrideForPathHighlight !== void 0) {
1852
+ const effectiveOverride = override ?? dashOverrideForPathHighlight;
1853
+ if (effectiveOverride === void 0) {
1854
+ out += " ";
1855
+ continue;
1856
+ }
1857
+ switch (cell.kind) {
1858
+ case "branch-tee":
1859
+ case "merge-tee": {
1860
+ const pair = seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee;
1861
+ out += effectiveOverride(pair.slice(0, 1)) + effectiveOverride(pair.slice(1));
1862
+ seenTee = true;
1863
+ break;
1864
+ }
1865
+ case "branch-corner":
1866
+ out += effectiveOverride(palette.branchCorner);
1867
+ break;
1868
+ case "merge-corner":
1869
+ out += effectiveOverride(palette.mergeCorner);
1870
+ break;
1871
+ case "vertical-pass":
1872
+ out += effectiveOverride(palette.verticalPass);
1873
+ break;
1874
+ case "horizontal-pass":
1875
+ out += effectiveOverride(palette.horizontalPass);
1876
+ break;
1877
+ case "arc-crossing": {
1878
+ const arcCrossingDashOverride = columnLaneOverride?.get(dashColumn) ?? effectiveOverride;
1879
+ out += effectiveOverride(palette.arcCrossing.slice(0, 1)) + arcCrossingDashOverride(palette.arcCrossing.slice(1));
1880
+ break;
1881
+ }
1882
+ default: out += " ";
1883
+ }
1884
+ continue;
1885
+ }
1651
1886
  const lane = laneStylerForColumn(glyphColumn, colorize, style);
1652
1887
  switch (cell.kind) {
1653
1888
  case "branch-tee":
@@ -1681,7 +1916,7 @@ function renderConnectorRow(row, gridWidth, colorize, style, palette) {
1681
1916
  }
1682
1917
  const start = row.startLane ?? 0;
1683
1918
  const end = row.endLane ?? start;
1684
- const runLane = laneStylerForColumn(end, colorize, style);
1919
+ const runLane = resolvedLane(end);
1685
1920
  let out = "";
1686
1921
  for (let column = 0; column < gridWidth; column++) if (column < start || column > end) out += " ";
1687
1922
  else if (column === start) out += runLane(palette.connectorBranchTee);
@@ -1699,7 +1934,7 @@ function overlayNamesForContract(contractHash, opts) {
1699
1934
  const refs = [];
1700
1935
  const userRefs = opts.refsByHash?.get(contractHash);
1701
1936
  if (userRefs) refs.push(...[...userRefs].sort((a, b) => a.localeCompare(b)));
1702
- if (opts.contractHash === contractHash && contractHash !== EMPTY_CONTRACT_HASH) markers.push(CONTRACT_MARKER_NAME);
1937
+ if (opts.isAppSpace !== false && opts.contractHash === contractHash && contractHash !== EMPTY_CONTRACT_HASH) markers.push(CONTRACT_MARKER_NAME);
1703
1938
  if (opts.dbHash === contractHash) markers.push(DB_MARKER_NAME);
1704
1939
  markers.sort((a, b) => {
1705
1940
  if (a === "contract") return -1;
@@ -1726,6 +1961,7 @@ function createTreeStyler(opts) {
1726
1961
  function formatEdgeAnnotationSuffix(migrationHash, opts, style) {
1727
1962
  const annotation = opts.edgeAnnotationsByHash?.get(migrationHash);
1728
1963
  if (annotation === void 0) return "";
1964
+ const isOffPath = annotation.pathHighlight === "off-path";
1729
1965
  const segments = [];
1730
1966
  if (annotation.operationCount !== void 0) segments.push(`${annotation.operationCount} ops`);
1731
1967
  if (annotation.invariants !== void 0 && annotation.invariants.length > 0) segments.push(style.invariants(annotation.invariants));
@@ -1740,15 +1976,31 @@ function formatEdgeAnnotationSuffix(migrationHash, opts, style) {
1740
1976
  segments.push(styler(`${glyph} ${label}`));
1741
1977
  }
1742
1978
  }
1979
+ if (annotation.pathHighlight === "on-path") {
1980
+ const glyph = opts.glyphMode === "ascii" ? ">" : "↑";
1981
+ segments.push(`${glyph} will run`);
1982
+ }
1743
1983
  if (segments.length === 0) return "";
1744
- return ` ${segments.join(" ")}`;
1984
+ const suffix = ` ${segments.join(" ")}`;
1985
+ return opts.colorize && isOffPath ? forcedDim(suffix) : suffix;
1745
1986
  }
1746
- function formatEdgeHashColumn(edge, style, hashLength, palette) {
1987
+ /**
1988
+ * Format the `from → to` hash data column for an edge row.
1989
+ *
1990
+ * When `hashOverride` is provided (off-path → `dim`), it replaces ALL sub-stylers
1991
+ * (`sourceHash`, `destHash`, arrow `glyph`) so dim reaches every character without
1992
+ * inner ANSI codes (e.g. the dim+cyan of `sourceHash`) overriding it. On-path edges
1993
+ * carry no override. Without an override, the normal `style` sub-stylers apply.
1994
+ */
1995
+ function formatEdgeHashColumn(edge, style, hashLength, palette, hashOverride) {
1996
+ const src = hashOverride ?? style.sourceHash;
1997
+ const dst = hashOverride ?? style.destHash;
1998
+ const glyph = hashOverride ?? style.glyph;
1747
1999
  if (edge.kind === "self") {
1748
2000
  const hash = abbreviateHash(edge.from, hashLength, palette.emptySource);
1749
- return `${padFromHashColumn(style.sourceHash(hash), hashLength)} ${style.glyph(palette.forwardArrow)} ${style.destHash(hash)}`;
2001
+ return `${padFromHashColumn(src(hash), hashLength)} ${glyph(palette.forwardArrow)} ${dst(hash)}`;
1750
2002
  }
1751
- return `${edge.from === EMPTY_CONTRACT_HASH ? padFromHashColumn(style.glyph(palette.emptySource), hashLength) : padFromHashColumn(style.sourceHash(abbreviateHash(edge.from, hashLength, palette.emptySource)), hashLength)} ${style.glyph(palette.forwardArrow)} ${style.destHash(abbreviateHash(edge.to, hashLength, palette.emptySource))}`;
2003
+ return `${edge.from === EMPTY_CONTRACT_HASH ? padFromHashColumn(glyph(palette.emptySource), hashLength) : padFromHashColumn(src(abbreviateHash(edge.from, hashLength, palette.emptySource)), hashLength)} ${glyph(palette.forwardArrow)} ${dst(abbreviateHash(edge.to, hashLength, palette.emptySource))}`;
1752
2004
  }
1753
2005
  function padVisible(text, targetWidth) {
1754
2006
  const padding = Math.max(0, targetWidth - stringWidth(text));
@@ -1807,6 +2059,60 @@ function renderMigrationGraphTree(model, opts) {
1807
2059
  const effectiveMaxDirNameLen = opts.globalMaxDirNameWidth ?? maxDirNameLen;
1808
2060
  const maxEdgePrefixWidth = opts.globalMaxEdgeTreePrefixWidth ?? maxEdgeTreePrefixWidth(model.rows, wideLabelColumn);
1809
2061
  const edgeDirNameWidth = rowDirNameWidth(maxEdgePrefixWidth, effectiveMaxDirNameLen, dirNameGap);
2062
+ const contractHighlights = /* @__PURE__ */ new Map();
2063
+ if (opts.edgeAnnotationsByHash) for (const row of model.rows) {
2064
+ if (row.kind !== "edge" || row.edge === void 0) continue;
2065
+ const annotation = opts.edgeAnnotationsByHash.get(row.edge.migrationHash);
2066
+ if (annotation?.pathHighlight === void 0) continue;
2067
+ const highlight = annotation.pathHighlight;
2068
+ for (const hash of [row.edge.from, row.edge.to]) {
2069
+ if (hash === EMPTY_CONTRACT_HASH) continue;
2070
+ if (contractHighlights.get(hash) !== "on-path") contractHighlights.set(hash, highlight);
2071
+ }
2072
+ }
2073
+ const pathHighlightActive = opts.edgeAnnotationsByHash !== void 0;
2074
+ /**
2075
+ * Resolve the lane and arrow overrides for a row in path-highlight mode.
2076
+ * - on-path → neutral single-path style (style.lane for glyphs, plain arrow, bold name).
2077
+ * Rotation colour is suppressed; `style.sourceHash`/`style.destHash` apply for hashes.
2078
+ * - off-path → uniform dim grey (forcedDim) on every glyph, arrow, name, and hash.
2079
+ * - undefined → `undefined` (no override). Unannotated rows use normal rotation. This covers
2080
+ * both non-path-highlight commands (graph/status/list) and any annotation without pathHighlight.
2081
+ * - When pathHighlightActive is false: always returns undefined, preserving normal rotation.
2082
+ */
2083
+ function pathStyleForHighlight(highlight) {
2084
+ if (!pathHighlightActive || highlight === void 0) return void 0;
2085
+ if (highlight === "off-path") {
2086
+ const s = PATH_HIGHLIGHT_STYLES.offPath(opts.colorize);
2087
+ return {
2088
+ lane: s.lane,
2089
+ arrow: s.arrow,
2090
+ dirName: s.dirName,
2091
+ hashOverride: s.hashOverride
2092
+ };
2093
+ }
2094
+ const s = PATH_HIGHLIGHT_STYLES.onPath(style, opts.colorize);
2095
+ return {
2096
+ lane: s.lane,
2097
+ arrow: s.arrow,
2098
+ dirName: s.dirName,
2099
+ hashOverride: s.hashOverride
2100
+ };
2101
+ }
2102
+ /**
2103
+ * Lane override for a given highlight in path-highlight mode.
2104
+ * Returns the `lane` part only — used for per-cell overrides.
2105
+ */
2106
+ function pathLaneFor(highlight) {
2107
+ return pathStyleForHighlight(highlight)?.lane;
2108
+ }
2109
+ /**
2110
+ * Arrow override for a given highlight in path-highlight mode.
2111
+ * Returns the `arrow` part only — used for edge-lane cell arrow rendering.
2112
+ */
2113
+ function pathArrowFor(highlight) {
2114
+ return pathStyleForHighlight(highlight)?.arrow;
2115
+ }
1810
2116
  const lines = [];
1811
2117
  for (let rowIndex = 0; rowIndex < model.rows.length; rowIndex++) {
1812
2118
  const row = model.rows[rowIndex];
@@ -1816,11 +2122,58 @@ function renderMigrationGraphTree(model, opts) {
1816
2122
  continue;
1817
2123
  }
1818
2124
  if (row.kind === "branch-connector" || row.kind === "merge-connector") {
1819
- lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)));
2125
+ let connectorColumnOverride;
2126
+ if (pathHighlightActive && opts.colorize) {
2127
+ connectorColumnOverride = /* @__PURE__ */ new Map();
2128
+ for (let col = 0; col < row.cells.length; col++) {
2129
+ const cell = row.cells[col];
2130
+ if (cell === void 0 || cell.kind === "empty") continue;
2131
+ const hashForCell = "migrationHash" in cell && cell.migrationHash !== void 0 ? cell.migrationHash : void 0;
2132
+ if (hashForCell === void 0) continue;
2133
+ const highlight = opts.edgeAnnotationsByHash?.get(hashForCell)?.pathHighlight;
2134
+ const override = pathLaneFor(highlight);
2135
+ if (override !== void 0) connectorColumnOverride.set(col, override);
2136
+ }
2137
+ if (connectorColumnOverride.size === 0) connectorColumnOverride = void 0;
2138
+ }
2139
+ lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette, connectorColumnOverride)));
1820
2140
  continue;
1821
2141
  }
2142
+ let rowPathHighlight;
2143
+ if (row.kind === "edge" && row.edge !== void 0) rowPathHighlight = opts.edgeAnnotationsByHash?.get(row.edge.migrationHash)?.pathHighlight;
2144
+ else if (row.kind === "node" && row.contractHash !== void 0) rowPathHighlight = contractHighlights.get(row.contractHash);
2145
+ const rowStyle = pathStyleForHighlight(rowPathHighlight);
2146
+ const rowLaneOverride = rowStyle?.lane;
2147
+ const rowArrowOverride = rowStyle?.arrow;
1822
2148
  const cellColors = resolveRowArcLaneColors(row.cells);
1823
- let gutter = row.cells.map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
2149
+ let gutter = row.cells.map((cell, column) => {
2150
+ let laneOverride = rowLaneOverride;
2151
+ let arrowOverride = rowArrowOverride;
2152
+ let arcLaneOverride;
2153
+ if (pathHighlightActive) {
2154
+ if (cell.kind === "edge-lane") {
2155
+ const cellHighlight = opts.edgeAnnotationsByHash?.get(cell.migrationHash)?.pathHighlight;
2156
+ laneOverride = pathLaneFor(cellHighlight);
2157
+ arrowOverride = pathArrowFor(cellHighlight);
2158
+ } else if (cell.kind === "node" && (cell.arcTee === true || cell.arcLand === true)) {
2159
+ const arcColumn = cellColors.connector[column] ?? 0;
2160
+ const arcCell = row.cells[arcColumn];
2161
+ const arcHash = arcCell !== void 0 && "migrationHash" in arcCell ? arcCell.migrationHash : void 0;
2162
+ if (arcHash !== void 0) {
2163
+ const arcHighlight = opts.edgeAnnotationsByHash?.get(arcHash)?.pathHighlight;
2164
+ arcLaneOverride = pathLaneFor(arcHighlight);
2165
+ }
2166
+ } else if (cell.kind !== "node" && cell.kind !== "empty") {
2167
+ const hashForCell = cell.kind === "arc-crossing" && "arcMigrationHash" in cell && cell.arcMigrationHash !== void 0 ? cell.arcMigrationHash : "migrationHash" in cell && cell.migrationHash !== void 0 ? cell.migrationHash : void 0;
2168
+ if (hashForCell !== void 0) {
2169
+ const cellHighlight = opts.edgeAnnotationsByHash?.get(hashForCell)?.pathHighlight;
2170
+ laneOverride = pathLaneFor(cellHighlight);
2171
+ arrowOverride = pathArrowFor(cellHighlight);
2172
+ }
2173
+ }
2174
+ }
2175
+ return renderCellPair(cell, column, cellColors, opts.colorize, style, palette, laneOverride, arrowOverride, arcLaneOverride);
2176
+ }).join("");
1824
2177
  let laneSpan = row.cells.length;
1825
2178
  if (row.kind === "node") if ((row.contractHash ?? EMPTY_CONTRACT_HASH) === EMPTY_CONTRACT_HASH) laneSpan = 1;
1826
2179
  else {
@@ -1832,15 +2185,39 @@ function renderMigrationGraphTree(model, opts) {
1832
2185
  laneSpan = lastActiveColumn >= 0 ? lastActiveColumn + 1 : 1;
1833
2186
  }
1834
2187
  const labelColumn = row.kind === "edge" ? maxEdgePrefixWidth : wideLabelColumn !== void 0 && (nodeHasArcDecoration(row) || row.contractHash !== void 0) ? wideLabelColumn : laneSpan * 2 + LABEL_GAP;
1835
- if (row.kind === "edge" && row.edge?.from === EMPTY_CONTRACT_HASH && (row.laneIndex ?? 0) === 0) gutter = row.cells.slice(0, 1).map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1836
- else if (row.kind === "node" && laneSpan < row.cells.length && !nodeHasArcDecoration(row)) gutter = row.cells.slice(0, laneSpan).map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
2188
+ if (row.kind === "edge" && row.edge?.from === EMPTY_CONTRACT_HASH && (row.laneIndex ?? 0) === 0) gutter = row.cells.slice(0, 1).map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette, rowLaneOverride, rowArrowOverride)).join("");
2189
+ else if (row.kind === "node" && laneSpan < row.cells.length && !nodeHasArcDecoration(row)) gutter = row.cells.slice(0, laneSpan).map((cell, column) => {
2190
+ let cellLaneOverride = rowLaneOverride;
2191
+ let cellArrowOverride = rowArrowOverride;
2192
+ if (pathHighlightActive && cell.kind !== "node" && cell.kind !== "empty") {
2193
+ const hashForCell = cell.kind === "arc-crossing" && "arcMigrationHash" in cell && cell.arcMigrationHash !== void 0 ? cell.arcMigrationHash : "migrationHash" in cell && cell.migrationHash !== void 0 ? cell.migrationHash : void 0;
2194
+ if (hashForCell !== void 0) {
2195
+ const cellHighlight = opts.edgeAnnotationsByHash?.get(hashForCell)?.pathHighlight;
2196
+ cellLaneOverride = pathLaneFor(cellHighlight);
2197
+ cellArrowOverride = pathArrowFor(cellHighlight);
2198
+ }
2199
+ }
2200
+ return renderCellPair(cell, column, cellColors, opts.colorize, style, palette, cellLaneOverride, cellArrowOverride);
2201
+ }).join("");
1837
2202
  else if (gutter.length < laneSpan * 2) gutter = gutter.padEnd(laneSpan * 2, " ");
1838
2203
  const dirNameWidth = row.kind === "edge" ? edgeDirNameWidth : rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
1839
2204
  const gutterPad = padVisible(gutter, labelColumn);
1840
2205
  if (row.kind === "node") {
1841
2206
  const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
1842
2207
  if (contractHash === EMPTY_CONTRACT_HASH) {
1843
- const trailingLanes = row.cells.slice(1).map((cell, offset) => renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette)).join("");
2208
+ const trailingLanes = row.cells.slice(1).map((cell, offset) => {
2209
+ let cellLaneOverride = rowLaneOverride;
2210
+ let cellArrowOverride = rowArrowOverride;
2211
+ if (pathHighlightActive && cell.kind !== "node" && cell.kind !== "empty") {
2212
+ const hashForCell = cell.kind === "arc-crossing" && "arcMigrationHash" in cell && cell.arcMigrationHash !== void 0 ? cell.arcMigrationHash : "migrationHash" in cell && cell.migrationHash !== void 0 ? cell.migrationHash : void 0;
2213
+ if (hashForCell !== void 0) {
2214
+ const cellHighlight = opts.edgeAnnotationsByHash?.get(hashForCell)?.pathHighlight;
2215
+ cellLaneOverride = pathLaneFor(cellHighlight);
2216
+ cellArrowOverride = pathArrowFor(cellHighlight);
2217
+ }
2218
+ }
2219
+ return renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette, cellLaneOverride, cellArrowOverride);
2220
+ }).join("");
1844
2221
  const emptyGutter = palette.emptySource.padEnd(2, " ") + trailingLanes;
1845
2222
  const overlays = overlayNamesForContract(contractHash, opts);
1846
2223
  if (overlays.markers.length === 0 && overlays.refs.length === 0) {
@@ -1851,7 +2228,7 @@ function renderMigrationGraphTree(model, opts) {
1851
2228
  lines.push(trimTrailingWhitespace(`${emptyGutter}${" ".repeat(LABEL_GAP)}${overlay}`));
1852
2229
  continue;
1853
2230
  }
1854
- const hashText = style.sourceHash(abbreviateHash(contractHash, hashLength, palette.emptySource));
2231
+ const hashText = (rowStyle?.hashOverride ?? style.sourceHash)(abbreviateHash(contractHash, hashLength, palette.emptySource));
1855
2232
  const overlays = overlayNamesForContract(contractHash, opts);
1856
2233
  const hasOverlays = overlays.markers.length > 0 || overlays.refs.length > 0;
1857
2234
  const overlayPad = hasOverlays ? " ".repeat(LABEL_GAP) : "";
@@ -1863,19 +2240,44 @@ function renderMigrationGraphTree(model, opts) {
1863
2240
  if (edge === void 0) continue;
1864
2241
  const dirNamePadding = " ".repeat(Math.max(0, dirNameWidth - edge.dirName.length));
1865
2242
  const laneIndex = row.laneIndex ?? 0;
1866
- const dirName = `${(opts.colorize && laneIndex > 0 ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
1867
- const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
2243
+ const edgeGutterPad = padVisible(gutter, labelColumn);
2244
+ let dirName;
2245
+ if (rowStyle !== void 0) dirName = `${(rowStyle.dirName ?? style.dirName)(edge.dirName)}${dirNamePadding}`;
2246
+ else dirName = `${(opts.colorize && laneIndex > 0 ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
2247
+ const hashColumnOverride = rowStyle?.hashOverride;
2248
+ const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette, hashColumnOverride);
1868
2249
  const annotationSuffix = formatEdgeAnnotationSuffix(edge.migrationHash, opts, style);
1869
- lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}${annotationSuffix}`));
2250
+ lines.push(trimTrailingWhitespace(`${edgeGutterPad}${dirName}${hashColumn}${annotationSuffix}`));
1870
2251
  }
1871
2252
  return lines.join("\n");
1872
2253
  }
2254
+ /**
2255
+ * Format a single on-path migration row for the `migrate --show` run-list.
2256
+ *
2257
+ * Uses the SAME styling as the tree renderer's on-path rows (PATH_HIGHLIGHT_STYLES.onPath)
2258
+ * so the run-list and graph tree are byte-for-byte identical in their name/hash columns.
2259
+ * The gutter is omitted — the list has no graph structure.
2260
+ *
2261
+ * This is the SINGLE code path for on-path row styling shared by both the graph tree
2262
+ * and the "Will run, in order:" list. To change the on-path colour, edit PATH_HIGHLIGHT_STYLES.
2263
+ */
2264
+ function formatOnPathMigrationRow(dirName, from, to, dirNameWidth, colorize, glyphMode) {
2265
+ const palette = paletteFor(glyphMode);
2266
+ const style = createAnsiMigrationListStyler({ useColor: colorize });
2267
+ const styledDirName = `${PATH_HIGHLIGHT_STYLES.onPath(style, colorize).dirName(dirName)}${" ".repeat(Math.max(0, dirNameWidth - dirName.length))}`;
2268
+ const hashLength = 7;
2269
+ const emptySource = palette.emptySource;
2270
+ const fromAbbr = from === EMPTY_CONTRACT_HASH ? padFromHashColumn(style.glyph(emptySource), hashLength) : padFromHashColumn(style.sourceHash(abbreviateHashShort(from, hashLength)), hashLength);
2271
+ const toAbbr = to === EMPTY_CONTRACT_HASH ? style.glyph(emptySource) : style.destHash(abbreviateHashShort(to, hashLength));
2272
+ return `${styledDirName} ${fromAbbr} ${style.glyph(palette.forwardArrow)} ${toAbbr}`;
2273
+ }
2274
+ function abbreviateHashShort(hash, length) {
2275
+ return (hash.startsWith("sha256:") ? hash.slice(7) : hash).slice(0, length);
2276
+ }
1873
2277
  function formatLegendExampleMarkers(colorize) {
1874
- if (!colorize) return "<contract, db>";
1875
- const open = green("<");
1876
- const close = green(">");
1877
- const separator = green(", ");
1878
- return open + green("contract") + separator + green("db") + close;
2278
+ if (!colorize) return "@contract @db";
2279
+ const sigil = green("@");
2280
+ return `${sigil + bold(green("contract"))} ${sigil}${green("db")}`;
1879
2281
  }
1880
2282
  /**
1881
2283
  * A compact key for the tree visual language: the contract node glyph, the
@@ -1903,7 +2305,7 @@ function renderMigrationGraphLegend(opts) {
1903
2305
  ` ${style.kind(palette.edgeArrow.self)} ${style.summary("migration without schema change")}`,
1904
2306
  appliedPending,
1905
2307
  ` ${style.kind(palette.emptySource)} ${style.summary("empty database (baseline)")}`,
1906
- ` ${exampleMarkers} ${style.summary("live markers (contract on disk, database state)")}`,
2308
+ ` ${exampleMarkers} ${style.summary("reserved markers also typeable as --from/--to tokens")}`,
1907
2309
  ` ${exampleRefs} ${style.summary("user-defined refs")}`,
1908
2310
  ` ${sampleArrow} ${style.summary("migration from contract aaaaaa to bbbbbb")}`
1909
2311
  ].join("\n");
@@ -1938,12 +2340,14 @@ function computeGlobalMaxDirNameWidth(inputs) {
1938
2340
  return globalMax;
1939
2341
  }
1940
2342
  function renderMigrationGraphSpaceTreeInternal(input) {
1941
- const layout = buildMigrationGraphLayout(buildMigrationGraphRows(input.graph, { contractHash: input.liveContractHash }));
2343
+ const appSpace = input.isAppSpace !== false;
2344
+ const layout = buildMigrationGraphLayout(buildMigrationGraphRows(input.graph, { ...appSpace ? { contractHash: input.liveContractHash } : {} }));
1942
2345
  const listOverlay = buildEdgeAnnotationsByHashFromListEntries(input.migrations);
1943
2346
  const edgeAnnotationsByHash = input.statusOverlayByHash === void 0 ? listOverlay : mergeMigrationEdgeAnnotations(listOverlay, input.statusOverlayByHash);
1944
2347
  return renderMigrationGraphTree(layout, {
1945
2348
  refsByHash: input.refsByHash ?? buildRefsByHashFromListEntries(input.migrations),
1946
2349
  contractHash: input.liveContractHash,
2350
+ isAppSpace: appSpace,
1947
2351
  edgeAnnotationsByHash,
1948
2352
  colorize: input.colorize,
1949
2353
  glyphMode: input.glyphMode,
@@ -1961,6 +2365,6 @@ function indentMigrationGraphTreeBlock(treeOutput, indent) {
1961
2365
  return treeOutput.split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
1962
2366
  }
1963
2367
  //#endregion
1964
- export { renderMigrationGraphLegend as a, renderMigrationListWithStyle as c, migrationListForwardArrow as d, renderMigrationGraphSpaceTree as i, abbreviateContractHash as l, computeGlobalMaxEdgeTreePrefixWidth as n, createAnsiMigrationListStyler as o, indentMigrationGraphTreeBlock as r, IDENTITY_MIGRATION_LIST_STYLER as s, computeGlobalMaxDirNameWidth as t, migrationListEmptySource as u };
2368
+ export { buildMigrationGraphLayout as _, computeMaxDirNameLengthForLayout as a, renderMigrationGraphLegend as c, IDENTITY_MIGRATION_LIST_STYLER as d, renderMigrationListWithStyle as f, buildMigrationGraphRows as g, migrationListForwardArrow as h, renderMigrationGraphSpaceTree as i, renderMigrationGraphTree as l, migrationListEmptySource as m, computeGlobalMaxEdgeTreePrefixWidth as n, computeMaxEdgeTreePrefixWidthForLayout as o, abbreviateContractHash as p, indentMigrationGraphTreeBlock as r, formatOnPathMigrationRow as s, computeGlobalMaxDirNameWidth as t, createAnsiMigrationListStyler as u };
1965
2369
 
1966
- //# sourceMappingURL=migration-graph-space-render-CeNXh_Wy.mjs.map
2370
+ //# sourceMappingURL=migration-graph-space-render-B0HkTNj3.mjs.map