prisma-next 0.12.0-dev.27 → 0.12.0-dev.29

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 (97) hide show
  1. package/dist/cli.mjs +12 -12
  2. package/dist/{client-V7BkIQrQ.mjs → client-xeWpMlq1.mjs} +22 -11
  3. package/dist/client-xeWpMlq1.mjs.map +1 -0
  4. package/dist/{command-helpers-DlrUCI7s.mjs → command-helpers-DK_5ItoJ.mjs} +253 -2
  5. package/dist/command-helpers-DK_5ItoJ.mjs.map +1 -0
  6. package/dist/commands/contract-emit.mjs +1 -1
  7. package/dist/commands/contract-infer.mjs +1 -1
  8. package/dist/commands/db-init.mjs +4 -5
  9. package/dist/commands/db-init.mjs.map +1 -1
  10. package/dist/commands/db-schema.mjs +3 -3
  11. package/dist/commands/db-sign.mjs +4 -4
  12. package/dist/commands/db-update.d.mts.map +1 -1
  13. package/dist/commands/db-update.mjs +10 -7
  14. package/dist/commands/db-update.mjs.map +1 -1
  15. package/dist/commands/db-verify.mjs +1 -1
  16. package/dist/commands/migrate.d.mts +1 -1
  17. package/dist/commands/migrate.mjs +5 -6
  18. package/dist/commands/migrate.mjs.map +1 -1
  19. package/dist/commands/migration-check.mjs +1 -1
  20. package/dist/commands/migration-graph.d.mts +12 -15
  21. package/dist/commands/migration-graph.d.mts.map +1 -1
  22. package/dist/commands/migration-graph.mjs +84 -51
  23. package/dist/commands/migration-graph.mjs.map +1 -1
  24. package/dist/commands/migration-list.d.mts +14 -5
  25. package/dist/commands/migration-list.d.mts.map +1 -1
  26. package/dist/commands/migration-list.mjs +1 -187
  27. package/dist/commands/migration-log.d.mts +2 -2
  28. package/dist/commands/migration-log.mjs +1 -1
  29. package/dist/commands/migration-new.mjs +3 -3
  30. package/dist/commands/migration-plan.mjs +1 -1
  31. package/dist/commands/migration-show.d.mts +1 -1
  32. package/dist/commands/migration-show.mjs +3 -4
  33. package/dist/commands/migration-show.mjs.map +1 -1
  34. package/dist/commands/migration-status.d.mts +21 -3
  35. package/dist/commands/migration-status.d.mts.map +1 -1
  36. package/dist/commands/migration-status.mjs +2 -3
  37. package/dist/commands/ref.d.mts +1 -1
  38. package/dist/commands/ref.mjs +3 -3
  39. package/dist/commands/telemetry/index.mjs +1 -1
  40. package/dist/{contract-at-errors-DlZHXSkI.mjs → contract-at-errors-DG3kjgoz.mjs} +2 -2
  41. package/dist/{contract-at-errors-DlZHXSkI.mjs.map → contract-at-errors-DG3kjgoz.mjs.map} +1 -1
  42. package/dist/{contract-emit-S53EyBRV.mjs → contract-emit-BO0l6fnT.mjs} +3 -3
  43. package/dist/{contract-emit-S53EyBRV.mjs.map → contract-emit-BO0l6fnT.mjs.map} +1 -1
  44. package/dist/{contract-emit-CaKp92-Q.mjs → contract-emit-C0Bs0VRj.mjs} +3 -3
  45. package/dist/{contract-emit-CaKp92-Q.mjs.map → contract-emit-C0Bs0VRj.mjs.map} +1 -1
  46. package/dist/{contract-infer-Cebb-_Qx.mjs → contract-infer-2wtPflGH.mjs} +3 -3
  47. package/dist/{contract-infer-Cebb-_Qx.mjs.map → contract-infer-2wtPflGH.mjs.map} +1 -1
  48. package/dist/{contract-space-aggregate-loader-Dvl1SJ4C.mjs → contract-space-aggregate-loader-Dbr3-jHF.mjs} +3 -3
  49. package/dist/{contract-space-aggregate-loader-Dvl1SJ4C.mjs.map → contract-space-aggregate-loader-Dbr3-jHF.mjs.map} +1 -1
  50. package/dist/{db-verify-B1OoWEWn.mjs → db-verify-CxHiSiTG.mjs} +4 -4
  51. package/dist/{db-verify-B1OoWEWn.mjs.map → db-verify-CxHiSiTG.mjs.map} +1 -1
  52. package/dist/exports/control-api.d.mts +1 -1
  53. package/dist/exports/control-api.mjs +2 -2
  54. package/dist/exports/index.mjs +1 -1
  55. package/dist/{framework-components-DCAT1uUC.mjs → framework-components-CxOVKAAh.mjs} +2 -2
  56. package/dist/{framework-components-DCAT1uUC.mjs.map → framework-components-CxOVKAAh.mjs.map} +1 -1
  57. package/dist/{init-Kf3T4A4W.mjs → init-R272pxux.mjs} +3 -3
  58. package/dist/{init-Kf3T4A4W.mjs.map → init-R272pxux.mjs.map} +1 -1
  59. package/dist/{inspect-live-schema-DTqflZ8X.mjs → inspect-live-schema-RekOwfi5.mjs} +3 -3
  60. package/dist/{inspect-live-schema-DTqflZ8X.mjs.map → inspect-live-schema-RekOwfi5.mjs.map} +1 -1
  61. package/dist/{migration-check-Ccyd0QKb.mjs → migration-check-Dc0cOhKH.mjs} +2 -2
  62. package/dist/{migration-check-Ccyd0QKb.mjs.map → migration-check-Dc0cOhKH.mjs.map} +1 -1
  63. package/dist/{migration-command-scaffold-DI7_SFL0.mjs → migration-command-scaffold-ApB3NxWY.mjs} +3 -3
  64. package/dist/{migration-command-scaffold-DI7_SFL0.mjs.map → migration-command-scaffold-ApB3NxWY.mjs.map} +1 -1
  65. package/dist/{migration-graph-tree-render-DyDBuJEX.mjs → migration-graph-space-render-dmLLWift.mjs} +389 -210
  66. package/dist/migration-graph-space-render-dmLLWift.mjs.map +1 -0
  67. package/dist/migration-list-C5sXrl0U.mjs +228 -0
  68. package/dist/migration-list-C5sXrl0U.mjs.map +1 -0
  69. package/dist/{migration-list-types-DV9PBc7Z.d.mts → migration-list-types-DS63IdFd.d.mts} +1 -1
  70. package/dist/{migration-list-types-DV9PBc7Z.d.mts.map → migration-list-types-DS63IdFd.d.mts.map} +1 -1
  71. package/dist/{migration-log-Des4seHP.mjs → migration-log-DD_vCbYW.mjs} +4 -4
  72. package/dist/{migration-log-Des4seHP.mjs.map → migration-log-DD_vCbYW.mjs.map} +1 -1
  73. package/dist/{migration-plan-DxDTBzGS.mjs → migration-plan-CeTjQOIG.mjs} +5 -5
  74. package/dist/{migration-plan-DxDTBzGS.mjs.map → migration-plan-CeTjQOIG.mjs.map} +1 -1
  75. package/dist/{migration-status-CCwqA-vi.mjs → migration-status-qV8ctwPy.mjs} +61 -45
  76. package/dist/migration-status-qV8ctwPy.mjs.map +1 -0
  77. package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-V1o-9LVK.mjs} +1 -1
  78. package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-V1o-9LVK.mjs.map} +1 -1
  79. package/dist/{telemetry-LFFQmqHd.mjs → telemetry-S-NGi9U6.mjs} +2 -2
  80. package/dist/{telemetry-LFFQmqHd.mjs.map → telemetry-S-NGi9U6.mjs.map} +1 -1
  81. package/dist/{terminal-ui-C3xGyxW-.d.mts → terminal-ui-5Y6mrg93.d.mts} +1 -1
  82. package/dist/{terminal-ui-C3xGyxW-.d.mts.map → terminal-ui-5Y6mrg93.d.mts.map} +1 -1
  83. package/dist/{types-BdS8PoKM.d.mts → types-Mh7mdPHM.d.mts} +5 -1
  84. package/dist/types-Mh7mdPHM.d.mts.map +1 -0
  85. package/dist/{verify-C0TARc6h.mjs → verify-BdI-BgYi.mjs} +2 -2
  86. package/dist/{verify-C0TARc6h.mjs.map → verify-BdI-BgYi.mjs.map} +1 -1
  87. package/package.json +11 -11
  88. package/dist/client-V7BkIQrQ.mjs.map +0 -1
  89. package/dist/command-helpers-DlrUCI7s.mjs.map +0 -1
  90. package/dist/commands/migration-list.mjs.map +0 -1
  91. package/dist/migration-graph-tree-render-DyDBuJEX.mjs.map +0 -1
  92. package/dist/migration-status-CCwqA-vi.mjs.map +0 -1
  93. package/dist/migration-types-CAQ-0TEE.d.mts +0 -15
  94. package/dist/migration-types-CAQ-0TEE.d.mts.map +0 -1
  95. package/dist/migrations-B3H6RTXb.mjs +0 -228
  96. package/dist/migrations-B3H6RTXb.mjs.map +0 -1
  97. package/dist/types-BdS8PoKM.d.mts.map +0 -1
@@ -945,7 +945,12 @@ function compareNodesTipsFirst(a, b, rank) {
945
945
  * at the same rank — stable across edge-insertion order and correct under
946
946
  * diamonds, cross-links, and rollbacks.
947
947
  */
948
- function layerNodesByLongestForwardPath(componentNodes, topology, graph) {
948
+ function maxRank(rank) {
949
+ let max = 0;
950
+ for (const value of rank.values()) if (value > max) max = value;
951
+ return max;
952
+ }
953
+ function layerNodesByLongestForwardPath(componentNodes, topology, graph, contractHash) {
949
954
  const forwardOut = /* @__PURE__ */ new Map();
950
955
  for (const node of componentNodes) forwardOut.set(node, []);
951
956
  for (const edges of graph.forwardChain.values()) for (const edge of edges) {
@@ -975,6 +980,7 @@ function layerNodesByLongestForwardPath(componentNodes, topology, graph) {
975
980
  if (!changed) break;
976
981
  }
977
982
  for (const node of componentNodes) if (!rank.has(node)) rank.set(node, 0);
983
+ if (contractHash !== void 0 && contractHash !== EMPTY_CONTRACT_HASH && componentNodes.has(contractHash) && (forwardOut.get(contractHash) ?? []).length === 0) rank.set(contractHash, maxRank(rank) + 1);
978
984
  return [...componentNodes].sort((a, b) => compareNodesTipsFirst(a, b, rank));
979
985
  }
980
986
  /**
@@ -1000,6 +1006,77 @@ function layerNodesByLongestForwardPath(componentNodes, topology, graph) {
1000
1006
  function detachedContractHash(graph, contractHash) {
1001
1007
  return contractHash !== void 0 && contractHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(contractHash) ? contractHash : void 0;
1002
1008
  }
1009
+ function isForwardLeaf(node, edges) {
1010
+ return !edges.some((e) => e.kind === "forward" && e.from === node && e.from !== e.to);
1011
+ }
1012
+ function forwardReachableFrom(start, forwardTo) {
1013
+ const reachable = new Set([start]);
1014
+ const queue = [start];
1015
+ while (queue.length > 0) {
1016
+ const node = queue.shift();
1017
+ if (node === void 0) continue;
1018
+ for (const next of forwardTo.get(node) ?? []) if (!reachable.has(next)) {
1019
+ reachable.add(next);
1020
+ queue.push(next);
1021
+ }
1022
+ }
1023
+ return reachable;
1024
+ }
1025
+ function buildForwardToMap(edges) {
1026
+ const forwardTo = /* @__PURE__ */ new Map();
1027
+ for (const edge of edges) {
1028
+ if (edge.kind !== "forward" || edge.from === edge.to) continue;
1029
+ const bucket = forwardTo.get(edge.from);
1030
+ if (bucket) bucket.push(edge.to);
1031
+ else forwardTo.set(edge.from, [edge.to]);
1032
+ }
1033
+ return forwardTo;
1034
+ }
1035
+ function sortEdgesForContractHashTrunk(edges, contractHash) {
1036
+ if (contractHash === void 0 || contractHash === EMPTY_CONTRACT_HASH || !isForwardLeaf(contractHash, edges)) return edges;
1037
+ const preferredLeaf = contractHash;
1038
+ const forwardTo = buildForwardToMap(edges);
1039
+ const reachability = /* @__PURE__ */ new Map();
1040
+ function canReachContractHash(from) {
1041
+ let cached = reachability.get(from);
1042
+ if (cached === void 0) {
1043
+ cached = forwardReachableFrom(from, forwardTo);
1044
+ reachability.set(from, cached);
1045
+ }
1046
+ return cached.has(preferredLeaf);
1047
+ }
1048
+ function trunkBias(edge) {
1049
+ if (edge.kind !== "forward" || edge.from === edge.to) return 0;
1050
+ if (edge.to === preferredLeaf) return 2;
1051
+ if (canReachContractHash(edge.to)) return 1;
1052
+ return 0;
1053
+ }
1054
+ return edges.map((edge, index) => ({
1055
+ edge,
1056
+ index,
1057
+ bias: trunkBias(edge)
1058
+ })).sort((a, b) => {
1059
+ if (a.edge.from !== b.edge.from) return a.index - b.index;
1060
+ if (a.bias !== b.bias) return b.bias - a.bias;
1061
+ return a.index - b.index;
1062
+ }).map(({ edge }) => edge);
1063
+ }
1064
+ function rebuildEdgeLookupMaps(edges) {
1065
+ const edgesByFrom = /* @__PURE__ */ new Map();
1066
+ const edgesByTo = /* @__PURE__ */ new Map();
1067
+ for (const classified of edges) {
1068
+ const fromBucket = edgesByFrom.get(classified.from);
1069
+ if (fromBucket) fromBucket.push(classified);
1070
+ else edgesByFrom.set(classified.from, [classified]);
1071
+ const toBucket = edgesByTo.get(classified.to);
1072
+ if (toBucket) toBucket.push(classified);
1073
+ else edgesByTo.set(classified.to, [classified]);
1074
+ }
1075
+ return {
1076
+ edgesByFrom,
1077
+ edgesByTo
1078
+ };
1079
+ }
1003
1080
  function buildMigrationGraphRows(graph, options = {}) {
1004
1081
  const emptyModel = {
1005
1082
  nodes: [],
@@ -1016,32 +1093,25 @@ function buildMigrationGraphRows(graph, options = {}) {
1016
1093
  }
1017
1094
  const topology = classifyMigrationGraphTopology(graph);
1018
1095
  const edges = [];
1019
- const edgesByFrom = /* @__PURE__ */ new Map();
1020
- const edgesByTo = /* @__PURE__ */ new Map();
1021
1096
  for (const edgeList of graph.forwardChain.values()) for (const edge of edgeList) {
1022
1097
  const kind = topology.kindByMigrationHash.get(edge.migrationHash) ?? "forward";
1023
- const classified = {
1098
+ edges.push({
1024
1099
  migrationHash: edge.migrationHash,
1025
1100
  from: edge.from,
1026
1101
  to: edge.to,
1027
1102
  dirName: edge.dirName,
1028
1103
  kind
1029
- };
1030
- edges.push(classified);
1031
- const fromBucket = edgesByFrom.get(edge.from);
1032
- if (fromBucket) fromBucket.push(classified);
1033
- else edgesByFrom.set(edge.from, [classified]);
1034
- const toBucket = edgesByTo.get(edge.to);
1035
- if (toBucket) toBucket.push(classified);
1036
- else edgesByTo.set(edge.to, [classified]);
1104
+ });
1037
1105
  }
1106
+ const sortedEdges = sortEdgesForContractHashTrunk(edges, options.contractHash);
1107
+ const { edgesByFrom, edgesByTo } = rebuildEdgeLookupMaps(sortedEdges);
1038
1108
  const components = weaklyConnectedComponents(graph);
1039
1109
  const nodes = [];
1040
1110
  for (let i = 0; i < components.length; i++) {
1041
1111
  if (i > 0) nodes.push(null);
1042
1112
  const component = components[i];
1043
1113
  if (component === void 0) continue;
1044
- const ordered = layerNodesByLongestForwardPath(component, topology, graph);
1114
+ const ordered = layerNodesByLongestForwardPath(component, topology, graph, options.contractHash);
1045
1115
  for (const node of ordered) nodes.push(node);
1046
1116
  }
1047
1117
  const detached = detachedContractHash(graph, options.contractHash);
@@ -1051,7 +1121,7 @@ function buildMigrationGraphRows(graph, options = {}) {
1051
1121
  }
1052
1122
  return {
1053
1123
  nodes,
1054
- edges,
1124
+ edges: sortedEdges,
1055
1125
  edgesByFrom,
1056
1126
  edgesByTo
1057
1127
  };
@@ -1080,6 +1150,126 @@ function laneColorForColumn(column) {
1080
1150
  if (column <= 0) return (text) => text;
1081
1151
  return LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length] ?? ((text) => text);
1082
1152
  }
1153
+ /**
1154
+ * Style a structural glyph by its resolved colour column. Column 0 and the
1155
+ * neutral sentinel render dim (`dimLane`); columns ≥ 1 take a palette hue.
1156
+ */
1157
+ function stylerForLaneColumn(colorColumn, colorize, dimLane) {
1158
+ if (!colorize || colorColumn <= 0) return dimLane;
1159
+ return laneColorForColumn(colorColumn);
1160
+ }
1161
+ /**
1162
+ * Resolve per-cell colour columns for a node/arc row. Scanning right-to-left
1163
+ * lets each arc segment inherit the hue of the arc it leads into.
1164
+ */
1165
+ function resolveRowArcLaneColors(cells) {
1166
+ const lane = new Array(cells.length);
1167
+ const connector = new Array(cells.length);
1168
+ const dash = new Array(cells.length);
1169
+ let arcCorner = 0;
1170
+ let landingAnchor = 0;
1171
+ for (let column = cells.length - 1; column >= 0; column--) {
1172
+ const cell = cells[column];
1173
+ connector[column] = landingAnchor !== 0 ? landingAnchor : arcCorner;
1174
+ switch (cell?.kind) {
1175
+ case "arc-branch-corner":
1176
+ arcCorner = column;
1177
+ lane[column] = column;
1178
+ dash[column] = column;
1179
+ break;
1180
+ case "arc-land-corner":
1181
+ arcCorner = column;
1182
+ landingAnchor = column;
1183
+ lane[column] = column;
1184
+ dash[column] = column;
1185
+ break;
1186
+ case "arc-branch-tee":
1187
+ lane[column] = column;
1188
+ dash[column] = column;
1189
+ break;
1190
+ case "arc-land-tee":
1191
+ lane[column] = column;
1192
+ dash[column] = landingAnchor === 0 ? column : landingAnchor;
1193
+ landingAnchor = column;
1194
+ break;
1195
+ case "arc-crossing":
1196
+ case "arc-land-bridge": {
1197
+ const served = landingAnchor !== 0 ? landingAnchor : arcCorner;
1198
+ lane[column] = served;
1199
+ dash[column] = served;
1200
+ break;
1201
+ }
1202
+ case "horizontal-pass":
1203
+ lane[column] = arcCorner === 0 ? column : arcCorner;
1204
+ dash[column] = lane[column] ?? column;
1205
+ break;
1206
+ case "node":
1207
+ lane[column] = column;
1208
+ dash[column] = column;
1209
+ arcCorner = 0;
1210
+ landingAnchor = 0;
1211
+ break;
1212
+ default:
1213
+ lane[column] = column;
1214
+ dash[column] = column;
1215
+ arcCorner = 0;
1216
+ landingAnchor = 0;
1217
+ }
1218
+ }
1219
+ return {
1220
+ lane,
1221
+ connector,
1222
+ dash
1223
+ };
1224
+ }
1225
+ /**
1226
+ * Resolve per-cell connector colours. Scanning right-to-left, a corner or an
1227
+ * intermediate tee anchors its own lane, but a tee's trailing dash leads into
1228
+ * the branch on its right.
1229
+ */
1230
+ function resolveConnectorLaneColors(cells, startLane) {
1231
+ const glyph = new Array(cells.length);
1232
+ const dash = new Array(cells.length);
1233
+ let owner = 0;
1234
+ for (let column = cells.length - 1; column >= 0; column--) switch (cells[column]?.kind) {
1235
+ case "branch-corner":
1236
+ case "merge-corner":
1237
+ owner = column;
1238
+ glyph[column] = column;
1239
+ dash[column] = column;
1240
+ break;
1241
+ case "branch-tee":
1242
+ case "merge-tee":
1243
+ if (column === startLane) {
1244
+ const served = owner === 0 ? column : owner;
1245
+ glyph[column] = column;
1246
+ dash[column] = served;
1247
+ } else {
1248
+ dash[column] = owner === 0 ? column : owner;
1249
+ glyph[column] = column;
1250
+ owner = column;
1251
+ }
1252
+ break;
1253
+ case "arc-crossing":
1254
+ glyph[column] = column;
1255
+ dash[column] = owner === 0 ? column : owner;
1256
+ owner = column;
1257
+ break;
1258
+ case "horizontal-pass": {
1259
+ const served = owner === 0 ? column : owner;
1260
+ glyph[column] = served;
1261
+ dash[column] = served;
1262
+ break;
1263
+ }
1264
+ default:
1265
+ glyph[column] = column;
1266
+ dash[column] = column;
1267
+ }
1268
+ return {
1269
+ glyph,
1270
+ dash
1271
+ };
1272
+ }
1083
1273
  function migrationListForwardArrow(glyphMode) {
1084
1274
  return glyphMode === "ascii" ? "->" : "→";
1085
1275
  }
@@ -1089,6 +1279,10 @@ function migrationListEmptySource(glyphMode) {
1089
1279
  function abbreviateContractHash(hash) {
1090
1280
  return (hash.startsWith("sha256:") ? hash.slice(7) : hash).slice(0, 7);
1091
1281
  }
1282
+ function padFromHashColumn(text, width) {
1283
+ const padding = Math.max(0, width - stringWidth(text));
1284
+ return `${" ".repeat(padding)}${text}`;
1285
+ }
1092
1286
  //#endregion
1093
1287
  //#region src/utils/formatters/migration-list-render.ts
1094
1288
  const IDENTITY_MIGRATION_LIST_STYLER = {
@@ -1155,24 +1349,25 @@ function buildRefsByHashFromListEntries(entries) {
1155
1349
  function formatEmptyStateLine(spaceId, style) {
1156
1350
  return style.emptyState(`There are no migrations in migrations/${spaceId}/ yet`);
1157
1351
  }
1158
- function indentTreeBlock(treeOutput, indent) {
1159
- if (treeOutput.length === 0) return treeOutput;
1160
- return treeOutput.split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
1161
- }
1162
- function renderSpaceTreeBlock(spaceId, migrations, multiSpace, glyphMode, style, colorize) {
1352
+ function renderSpaceTreeBlock(spaceId, migrations, multiSpace, glyphMode, style, colorize, liveContractHash, graphForSpace, globalMaxEdgeTreePrefixWidth, globalMaxDirNameWidth) {
1163
1353
  if (migrations.length === 0) {
1164
1354
  const emptyLine = formatEmptyStateLine(spaceId, style);
1165
1355
  if (!multiSpace) return [emptyLine];
1166
1356
  return [style.spaceHeading(`${spaceId}:`), ` ${emptyLine}`];
1167
1357
  }
1168
- const treeOutput = renderMigrationGraphTree(buildMigrationGraphLayout(buildMigrationGraphRows(migrationGraphFromListEntries(migrations))), {
1169
- refsByHash: buildRefsByHashFromListEntries(migrations),
1170
- edgeAnnotationsByHash: buildEdgeAnnotationsByHashFromListEntries(migrations),
1358
+ const treeOutput = renderMigrationGraphSpaceTree({
1359
+ graph: graphForSpace(spaceId) ?? migrationGraphFromListEntries(migrations),
1360
+ migrations,
1361
+ liveContractHash,
1362
+ glyphMode,
1171
1363
  colorize,
1172
- glyphMode
1364
+ refsByHash: buildRefsByHashFromListEntries(migrations),
1365
+ styler: style,
1366
+ ...globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth } : {},
1367
+ ...globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth } : {}
1173
1368
  });
1174
1369
  if (!multiSpace) return treeOutput.length === 0 ? [] : [treeOutput];
1175
- const indented = indentTreeBlock(treeOutput, " ");
1370
+ const indented = indentMigrationGraphTreeBlock(treeOutput, " ");
1176
1371
  return [style.spaceHeading(`${spaceId}:`), indented];
1177
1372
  }
1178
1373
  /**
@@ -1186,11 +1381,19 @@ function renderSpaceTreeBlock(spaceId, migrations, multiSpace, glyphMode, style,
1186
1381
  function renderMigrationListWithStyle(result, style, glyphMode = "unicode", options = {}) {
1187
1382
  const multiSpace = result.spaces.length > 1;
1188
1383
  const colorize = options.colorize ?? false;
1384
+ const liveContractHash = options.liveContractHash ?? EMPTY_CONTRACT_HASH;
1385
+ const graphForSpace = options.graphForSpace ?? (() => void 0);
1386
+ const globalLayoutInputs = multiSpace ? result.spaces.filter((space) => space.migrations.length > 0).map((space) => ({
1387
+ graph: graphForSpace(space.spaceId) ?? migrationGraphFromListEntries(space.migrations),
1388
+ liveContractHash
1389
+ })) : [];
1390
+ const globalMaxEdgeTreePrefixWidth = globalLayoutInputs.length > 0 ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs) : void 0;
1391
+ const globalMaxDirNameWidth = globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : void 0;
1189
1392
  const lines = [];
1190
1393
  for (let index = 0; index < result.spaces.length; index++) {
1191
1394
  const space = result.spaces[index];
1192
1395
  if (index > 0) lines.push("");
1193
- lines.push(...renderSpaceTreeBlock(space.spaceId, space.migrations, multiSpace, glyphMode, style, colorize));
1396
+ lines.push(...renderSpaceTreeBlock(space.spaceId, space.migrations, multiSpace, glyphMode, style, colorize, liveContractHash, graphForSpace, globalMaxEdgeTreePrefixWidth, globalMaxDirNameWidth));
1194
1397
  }
1195
1398
  if (result.spaces.reduce((count, space) => count + space.migrations.length, 0) > 0) {
1196
1399
  lines.push("");
@@ -1200,6 +1403,21 @@ function renderMigrationListWithStyle(result, style, glyphMode = "unicode", opti
1200
1403
  }
1201
1404
  //#endregion
1202
1405
  //#region src/utils/formatters/migration-list-styler.ts
1406
+ function hasMarkersFormatter(styler) {
1407
+ return "markers" in styler && typeof styler.markers === "function";
1408
+ }
1409
+ function styleMarkerName(name) {
1410
+ return name === "contract" ? bold(green(name)) : green(name);
1411
+ }
1412
+ function plainMarkers(names) {
1413
+ return `<${names.join(", ")}>`;
1414
+ }
1415
+ function formatContractNodeOverlays(styler, markers, refs) {
1416
+ const parts = [];
1417
+ if (markers.length > 0) parts.push(hasMarkersFormatter(styler) ? styler.markers(markers) : plainMarkers(markers));
1418
+ if (refs.length > 0) parts.push(styler.refs(refs));
1419
+ return parts.join(" ");
1420
+ }
1203
1421
  /**
1204
1422
  * The current contract overlay marker. Unlike user refs, this names the user's
1205
1423
  * declared desired state — the implicit base/target for `plan` / `migrate` —
@@ -1208,7 +1426,7 @@ function renderMigrationListWithStyle(result, style, glyphMode = "unicode", opti
1208
1426
  */
1209
1427
  const CONTRACT_MARKER_NAME = "contract";
1210
1428
  function styleRefName(name) {
1211
- return name === "contract" ? bold(green(name)) : green(name);
1429
+ return green(name);
1212
1430
  }
1213
1431
  /**
1214
1432
  * Build a {@link MigrationListStyler} that decorates `migration list`
@@ -1226,14 +1444,18 @@ function styleRefName(name) {
1226
1444
  * - `glyph` (`→` / `⟲` / `∅`): dim
1227
1445
  * - `lane` (graph gutter lines `│` and fan/join connectors `├─┐` / `├─┘`): dim
1228
1446
  * - `invariants` (`{...}`): yellow
1229
- * - `refs` (`(...)`): green; the `contract` desired-state marker inside is
1230
- * green-bold (the active ref is bolded separately by the tree styler)
1447
+ * - `markers` (`<...>`): green; the `contract` desired-state marker inside is
1448
+ * green-bold (`db` is plain green)
1449
+ * - `refs` (`(...)`): green (the active ref is bolded separately by the tree styler)
1231
1450
  * - `spaceHeading` (`<spaceId>:`): bold
1232
1451
  * - `summary`: dim
1233
1452
  * - `emptyState`: dim
1234
1453
  */
1235
1454
  function createAnsiMigrationListStyler(opts) {
1236
- if (!opts.useColor) return IDENTITY_MIGRATION_LIST_STYLER;
1455
+ if (!opts.useColor) return {
1456
+ ...IDENTITY_MIGRATION_LIST_STYLER,
1457
+ markers: plainMarkers
1458
+ };
1237
1459
  return {
1238
1460
  kind: (text) => text,
1239
1461
  dirName: (text) => bold(text),
@@ -1242,6 +1464,12 @@ function createAnsiMigrationListStyler(opts) {
1242
1464
  glyph: (text) => dim(text),
1243
1465
  lane: (text) => dim(text),
1244
1466
  invariants: (ids) => yellow(`{${ids.join(", ")}}`),
1467
+ markers: (names) => {
1468
+ const open = green("<");
1469
+ const close = green(">");
1470
+ const separator = green(", ");
1471
+ return open + names.map(styleMarkerName).join(separator) + close;
1472
+ },
1245
1473
  refs: (names) => {
1246
1474
  const open = green("(");
1247
1475
  const close = green(")");
@@ -1332,161 +1560,14 @@ function arrowForEdgeKind(kind, palette) {
1332
1560
  return palette.edgeArrow[kind];
1333
1561
  }
1334
1562
  /**
1335
- * The leftmost lane (column 0) renders with the neutral dim lane style rather
1336
- * than a palette hue — in the common single-lane case it has nothing to be told
1337
- * apart from. Used as the "no owning arc" sentinel during colour resolution.
1338
- */
1339
- const NEUTRAL_LANE = 0;
1340
- /**
1341
1563
  * Forced bold for branch-coloured names. A branched name pairs its lane hue
1342
1564
  * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
1343
1565
  * when colorette's ambient TTY detection is off, so the colorized branch name
1344
1566
  * is deterministically bold + hue rather than hue-only.
1345
1567
  */
1346
1568
  const { bold: forcedBold } = createColors({ useColor: true });
1347
- /**
1348
- * Resolve per-cell colour columns for a row. Scanning right-to-left lets each
1349
- * arc segment inherit the hue of the arc it leads into.
1350
- *
1351
- * On a converging-landing line (`○◂──────┴─┴─╯`), every horizontal dash segment
1352
- * takes the hue of the **nearest landing anchor** — the next `arc-land-tee` or
1353
- * `arc-land-corner` — to its right, i.e. the branch it leads into: the bridge
1354
- * run leads into the first converging arc, and each tee's trailing `─` leads
1355
- * into the next arc out. Tee/corner junction glyphs keep their own column hue.
1356
- * This mirrors the forward connector's `┬─` rule (see
1357
- * {@link resolveConnectorLaneColors}). A single (non-converging) landing has
1358
- * only the corner as an anchor, so its whole horizontal run reads as one hue.
1359
- *
1360
- * The source side (`○─`, `arc-branch-tee`, `arc-branch-corner`) and pure
1361
- * horizontal passes are unaffected: they track the nearest corner to the right
1362
- * (`arcCorner`), so a routed back-arc's source fan still reads as one hue. A
1363
- * crossing can only be one colour, so it takes the arc owning the horizontal
1364
- * run at this row; the crossed vertical lane is occluded at that one cell and
1365
- * reappears on the next row.
1366
- */
1367
- function resolveRowLaneColors(cells) {
1368
- const lane = new Array(cells.length);
1369
- const connector = new Array(cells.length);
1370
- const dash = new Array(cells.length);
1371
- let arcCorner = NEUTRAL_LANE;
1372
- let landingAnchor = NEUTRAL_LANE;
1373
- for (let column = cells.length - 1; column >= 0; column--) {
1374
- const cell = cells[column];
1375
- connector[column] = landingAnchor !== NEUTRAL_LANE ? landingAnchor : arcCorner;
1376
- switch (cell?.kind) {
1377
- case "arc-branch-corner":
1378
- arcCorner = column;
1379
- lane[column] = column;
1380
- dash[column] = column;
1381
- break;
1382
- case "arc-land-corner":
1383
- arcCorner = column;
1384
- landingAnchor = column;
1385
- lane[column] = column;
1386
- dash[column] = column;
1387
- break;
1388
- case "arc-branch-tee":
1389
- lane[column] = column;
1390
- dash[column] = column;
1391
- break;
1392
- case "arc-land-tee":
1393
- lane[column] = column;
1394
- dash[column] = landingAnchor === NEUTRAL_LANE ? column : landingAnchor;
1395
- landingAnchor = column;
1396
- break;
1397
- case "arc-crossing":
1398
- case "arc-land-bridge": {
1399
- const served = landingAnchor !== NEUTRAL_LANE ? landingAnchor : arcCorner;
1400
- lane[column] = served;
1401
- dash[column] = served;
1402
- break;
1403
- }
1404
- case "horizontal-pass":
1405
- lane[column] = arcCorner === NEUTRAL_LANE ? column : arcCorner;
1406
- dash[column] = lane[column] ?? column;
1407
- break;
1408
- case "node":
1409
- lane[column] = column;
1410
- dash[column] = column;
1411
- arcCorner = NEUTRAL_LANE;
1412
- landingAnchor = NEUTRAL_LANE;
1413
- break;
1414
- default:
1415
- lane[column] = column;
1416
- dash[column] = column;
1417
- arcCorner = NEUTRAL_LANE;
1418
- landingAnchor = NEUTRAL_LANE;
1419
- }
1420
- }
1421
- return {
1422
- lane,
1423
- connector,
1424
- dash
1425
- };
1426
- }
1427
- /**
1428
- * Resolve per-cell connector colours. Scanning right-to-left, a corner or an
1429
- * intermediate tee anchors its own lane (its junction glyph takes that column),
1430
- * but a tee's **trailing dash leads into the branch on its right** (the next
1431
- * branch point), so `┬─` reads as "this lane, then on toward the next" rather
1432
- * than tinting the dash with the left lane. The leading tee at `startLane` (the
1433
- * fork/merge origin) and pure horizontal segments inherit the nearest branch
1434
- * point to their right whole-cell, so the run into a branch — or collapsing
1435
- * into a merge corner — stays continuous. An `arc-crossing` keeps its junction
1436
- * glyph at its own column but re-anchors `owner` like an intermediate tee so
1437
- * dashes on both sides lead into the nearest branch on their right. Pass-through
1438
- * verticals outside the run keep their own column (column 0 stays neutral).
1439
- */
1440
- function resolveConnectorLaneColors(cells, startLane) {
1441
- const glyph = new Array(cells.length);
1442
- const dash = new Array(cells.length);
1443
- let owner = NEUTRAL_LANE;
1444
- for (let column = cells.length - 1; column >= 0; column--) switch (cells[column]?.kind) {
1445
- case "branch-corner":
1446
- case "merge-corner":
1447
- owner = column;
1448
- glyph[column] = column;
1449
- dash[column] = column;
1450
- break;
1451
- case "branch-tee":
1452
- case "merge-tee":
1453
- if (column === startLane) {
1454
- const served = owner === NEUTRAL_LANE ? column : owner;
1455
- glyph[column] = column;
1456
- dash[column] = served;
1457
- } else {
1458
- dash[column] = owner === NEUTRAL_LANE ? column : owner;
1459
- glyph[column] = column;
1460
- owner = column;
1461
- }
1462
- break;
1463
- case "arc-crossing":
1464
- glyph[column] = column;
1465
- dash[column] = owner === NEUTRAL_LANE ? column : owner;
1466
- owner = column;
1467
- break;
1468
- case "horizontal-pass": {
1469
- const served = owner === NEUTRAL_LANE ? column : owner;
1470
- glyph[column] = served;
1471
- dash[column] = served;
1472
- break;
1473
- }
1474
- default:
1475
- glyph[column] = column;
1476
- dash[column] = column;
1477
- }
1478
- return {
1479
- glyph,
1480
- dash
1481
- };
1482
- }
1483
- /**
1484
- * Style a structural glyph by its resolved colour column. Column 0 and the
1485
- * neutral sentinel render dim (`style.lane`); columns ≥ 1 take a palette hue.
1486
- */
1487
1569
  function laneStylerForColumn(colorColumn, colorize, style) {
1488
- if (!colorize || colorColumn <= NEUTRAL_LANE) return (text) => style.lane(text);
1489
- return laneColorForColumn(colorColumn);
1570
+ return stylerForLaneColumn(colorColumn, colorize, style.lane);
1490
1571
  }
1491
1572
  /**
1492
1573
  * Tint a branch-owned token (direction arrow, migration name) by its edge's
@@ -1496,10 +1577,19 @@ function laneStylerForColumn(colorColumn, colorize, style) {
1496
1577
  * colour. With colour off, the fallback (also colourless) is used unchanged.
1497
1578
  */
1498
1579
  function branchStylerOrDefault(column, colorize, fallback) {
1499
- if (!colorize || column <= NEUTRAL_LANE) return fallback;
1580
+ if (!colorize || column <= 0) return fallback;
1500
1581
  return laneColorForColumn(column);
1501
1582
  }
1502
1583
  /**
1584
+ * Render a crossing tee (`┼─`): the junction stays dim/neutral so neither arc
1585
+ * steals the cell; the trailing dash takes the served lane hue.
1586
+ */
1587
+ function renderArcCrossing(pair, dashColumn, colorize, style) {
1588
+ const junction = colorize ? style.lane : (text) => text;
1589
+ const dash = laneStylerForColumn(dashColumn, colorize, style);
1590
+ return junction(pair.slice(0, 1)) + dash(pair.slice(1));
1591
+ }
1592
+ /**
1503
1593
  * Render a connector tee (`├─` / `┬─` / `┴─`) with its junction glyph and its
1504
1594
  * trailing dash coloured independently: the junction anchors its own lane while
1505
1595
  * the dash leads into the branch on its right.
@@ -1526,7 +1616,7 @@ function renderCellPair(cell, column, colors, colorize, style, palette) {
1526
1616
  const lane = laneStylerForColumn(laneColumn, colorize, style);
1527
1617
  switch (cell.kind) {
1528
1618
  case "node": {
1529
- const arcColumn = colors.connector[column] ?? NEUTRAL_LANE;
1619
+ const arcColumn = colors.connector[column] ?? 0;
1530
1620
  if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
1531
1621
  if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
1532
1622
  return lane(palette.node);
@@ -1581,7 +1671,7 @@ function renderConnectorRow(row, gridWidth, colorize, style, palette) {
1581
1671
  out += lane(palette.horizontalPass);
1582
1672
  break;
1583
1673
  case "arc-crossing":
1584
- out += renderConnectorTee(palette.arcCrossing, glyphColumn, dashColumn, colorize, style);
1674
+ out += renderArcCrossing(palette.arcCrossing, dashColumn, colorize, style);
1585
1675
  break;
1586
1676
  default: out += " ";
1587
1677
  }
@@ -1605,15 +1695,24 @@ function abbreviateHash(hash, hashLength, emptySource) {
1605
1695
  }
1606
1696
  const MIN_HASH_DATA_COLUMN = 25;
1607
1697
  function overlayNamesForContract(contractHash, opts) {
1608
- const names = [];
1698
+ const markers = [];
1699
+ const refs = [];
1609
1700
  const userRefs = opts.refsByHash?.get(contractHash);
1610
- if (userRefs) names.push(...[...userRefs].sort((a, b) => a.localeCompare(b)));
1611
- if (opts.dbHash === contractHash) names.push(DB_MARKER_NAME);
1612
- if (opts.contractHash === contractHash && contractHash !== EMPTY_CONTRACT_HASH) names.push(CONTRACT_MARKER_NAME);
1613
- return names;
1701
+ 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);
1703
+ if (opts.dbHash === contractHash) markers.push(DB_MARKER_NAME);
1704
+ markers.sort((a, b) => {
1705
+ if (a === "contract") return -1;
1706
+ if (b === "contract") return 1;
1707
+ return a.localeCompare(b);
1708
+ });
1709
+ return {
1710
+ markers,
1711
+ refs
1712
+ };
1614
1713
  }
1615
1714
  function createTreeStyler(opts) {
1616
- const base = createAnsiMigrationListStyler({ useColor: opts.colorize });
1715
+ const base = opts.styler ?? createAnsiMigrationListStyler({ useColor: opts.colorize });
1617
1716
  const activeRefName = opts.activeRefName;
1618
1717
  if (!opts.colorize || activeRefName === void 0) return base;
1619
1718
  return {
@@ -1628,6 +1727,8 @@ function formatEdgeAnnotationSuffix(migrationHash, opts, style) {
1628
1727
  const annotation = opts.edgeAnnotationsByHash?.get(migrationHash);
1629
1728
  if (annotation === void 0) return "";
1630
1729
  const segments = [];
1730
+ if (annotation.operationCount !== void 0) segments.push(`${annotation.operationCount} ops`);
1731
+ if (annotation.invariants !== void 0 && annotation.invariants.length > 0) segments.push(style.invariants(annotation.invariants));
1631
1732
  const status = annotation.status;
1632
1733
  if (status !== void 0) {
1633
1734
  const glyphs = overlayStatusGlyphs(opts.glyphMode ?? "unicode");
@@ -1639,17 +1740,15 @@ function formatEdgeAnnotationSuffix(migrationHash, opts, style) {
1639
1740
  segments.push(styler(`${glyph} ${label}`));
1640
1741
  }
1641
1742
  }
1642
- if (annotation.operationCount !== void 0) segments.push(`${annotation.operationCount} ops`);
1643
- if (annotation.invariants !== void 0 && annotation.invariants.length > 0) segments.push(style.invariants(annotation.invariants));
1644
1743
  if (segments.length === 0) return "";
1645
- return `${status !== void 0 ? " " : " "}${segments.join(" ")}`;
1744
+ return ` ${segments.join(" ")}`;
1646
1745
  }
1647
1746
  function formatEdgeHashColumn(edge, style, hashLength, palette) {
1648
1747
  if (edge.kind === "self") {
1649
1748
  const hash = abbreviateHash(edge.from, hashLength, palette.emptySource);
1650
- return `${style.sourceHash(hash)} ${style.glyph(palette.forwardArrow)} ${style.destHash(hash)}`;
1749
+ return `${padFromHashColumn(style.sourceHash(hash), hashLength)} ${style.glyph(palette.forwardArrow)} ${style.destHash(hash)}`;
1651
1750
  }
1652
- return `${edge.from === EMPTY_CONTRACT_HASH ? style.glyph(palette.emptySource) + " ".repeat(Math.max(0, hashLength - palette.emptySource.length)) : style.sourceHash(abbreviateHash(edge.from, hashLength, palette.emptySource))} ${style.glyph(palette.forwardArrow)} ${style.destHash(abbreviateHash(edge.to, hashLength, palette.emptySource))}`;
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))}`;
1653
1752
  }
1654
1753
  function padVisible(text, targetWidth) {
1655
1754
  const padding = Math.max(0, targetWidth - stringWidth(text));
@@ -1679,6 +1778,21 @@ function edgeLabelColumn(row, wideLabelColumn) {
1679
1778
  if (row.edge?.from === EMPTY_CONTRACT_HASH && laneIndex === 0) return (laneIndex + 1) * 2 + LABEL_GAP;
1680
1779
  return row.cells.some((cell, index) => index > laneIndex && cell.kind === "vertical-pass") ? row.cells.length * 2 + LABEL_GAP : (laneIndex + 1) * 2 + LABEL_GAP;
1681
1780
  }
1781
+ function maxEdgeTreePrefixWidth(rows, wideLabelColumn) {
1782
+ let max = 0;
1783
+ for (const row of rows) {
1784
+ if (row.kind !== "edge" || row.edge === void 0) continue;
1785
+ max = Math.max(max, edgeLabelColumn(row, wideLabelColumn));
1786
+ }
1787
+ return max;
1788
+ }
1789
+ function computeMaxEdgeTreePrefixWidthForLayout(model) {
1790
+ const wideLabelColumn = gridUsesSkipRollbackArcs(model.rows) ? gridWidthForModel(model.rows) * 2 + 4 : void 0;
1791
+ return maxEdgeTreePrefixWidth(model.rows, wideLabelColumn);
1792
+ }
1793
+ function computeMaxDirNameLengthForLayout(model) {
1794
+ return maxDirNameLength(model.rows.filter((row) => row.kind === "edge" && row.edge !== void 0).map((row) => row.edge));
1795
+ }
1682
1796
  function nodeHasArcDecoration(row) {
1683
1797
  return row.cells.some((cell) => cell.kind === "node" && (cell.arcTee === true || cell.arcLand === true));
1684
1798
  }
@@ -1690,6 +1804,9 @@ function renderMigrationGraphTree(model, opts) {
1690
1804
  const wideLabelColumn = gridUsesSkipRollbackArcs(model.rows) ? gridWidth * 2 + 4 : void 0;
1691
1805
  const dirNameGap = wideLabelColumn !== void 0 ? 3 : LABEL_GAP;
1692
1806
  const maxDirNameLen = maxDirNameLength(model.rows.filter((row) => row.kind === "edge" && row.edge !== void 0).map((row) => row.edge));
1807
+ const effectiveMaxDirNameLen = opts.globalMaxDirNameWidth ?? maxDirNameLen;
1808
+ const maxEdgePrefixWidth = opts.globalMaxEdgeTreePrefixWidth ?? maxEdgeTreePrefixWidth(model.rows, wideLabelColumn);
1809
+ const edgeDirNameWidth = rowDirNameWidth(maxEdgePrefixWidth, effectiveMaxDirNameLen, dirNameGap);
1693
1810
  const lines = [];
1694
1811
  for (let rowIndex = 0; rowIndex < model.rows.length; rowIndex++) {
1695
1812
  const row = model.rows[rowIndex];
@@ -1702,7 +1819,7 @@ function renderMigrationGraphTree(model, opts) {
1702
1819
  lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)));
1703
1820
  continue;
1704
1821
  }
1705
- const cellColors = resolveRowLaneColors(row.cells);
1822
+ const cellColors = resolveRowArcLaneColors(row.cells);
1706
1823
  let gutter = row.cells.map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1707
1824
  let laneSpan = row.cells.length;
1708
1825
  if (row.kind === "node") if ((row.contractHash ?? EMPTY_CONTRACT_HASH) === EMPTY_CONTRACT_HASH) laneSpan = 1;
@@ -1714,31 +1831,31 @@ function renderMigrationGraphTree(model, opts) {
1714
1831
  }
1715
1832
  laneSpan = lastActiveColumn >= 0 ? lastActiveColumn + 1 : 1;
1716
1833
  }
1717
- const labelColumn = row.kind === "edge" ? edgeLabelColumn(row, wideLabelColumn) : wideLabelColumn !== void 0 && (nodeHasArcDecoration(row) || row.contractHash !== void 0) ? wideLabelColumn : laneSpan * 2 + LABEL_GAP;
1834
+ const labelColumn = row.kind === "edge" ? maxEdgePrefixWidth : wideLabelColumn !== void 0 && (nodeHasArcDecoration(row) || row.contractHash !== void 0) ? wideLabelColumn : laneSpan * 2 + LABEL_GAP;
1718
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("");
1719
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("");
1720
1837
  else if (gutter.length < laneSpan * 2) gutter = gutter.padEnd(laneSpan * 2, " ");
1721
- const dirNameWidth = rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
1722
- const dataColumn = labelColumn + dirNameWidth;
1838
+ const dirNameWidth = row.kind === "edge" ? edgeDirNameWidth : rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
1723
1839
  const gutterPad = padVisible(gutter, labelColumn);
1724
1840
  if (row.kind === "node") {
1725
1841
  const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
1726
1842
  if (contractHash === EMPTY_CONTRACT_HASH) {
1727
1843
  const trailingLanes = row.cells.slice(1).map((cell, offset) => renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette)).join("");
1728
1844
  const emptyGutter = palette.emptySource.padEnd(2, " ") + trailingLanes;
1729
- const overlayNames = overlayNamesForContract(contractHash, opts);
1730
- if (overlayNames.length === 0) {
1845
+ const overlays = overlayNamesForContract(contractHash, opts);
1846
+ if (overlays.markers.length === 0 && overlays.refs.length === 0) {
1731
1847
  lines.push(trimTrailingWhitespace(emptyGutter));
1732
1848
  continue;
1733
1849
  }
1734
- const overlay = style.refs(overlayNames);
1735
- lines.push(trimTrailingWhitespace(`${padVisible(emptyGutter, dataColumn)}${overlay}`));
1850
+ const overlay = formatContractNodeOverlays(style, overlays.markers, overlays.refs);
1851
+ lines.push(trimTrailingWhitespace(`${emptyGutter}${" ".repeat(LABEL_GAP)}${overlay}`));
1736
1852
  continue;
1737
1853
  }
1738
1854
  const hashText = style.sourceHash(abbreviateHash(contractHash, hashLength, palette.emptySource));
1739
- const overlayNames = overlayNamesForContract(contractHash, opts);
1740
- const overlayPad = overlayNames.length > 0 ? " ".repeat(Math.max(0, dataColumn - labelColumn - stringWidth(hashText))) : "";
1741
- const overlay = overlayNames.length > 0 ? style.refs(overlayNames) : "";
1855
+ const overlays = overlayNamesForContract(contractHash, opts);
1856
+ const hasOverlays = overlays.markers.length > 0 || overlays.refs.length > 0;
1857
+ const overlayPad = hasOverlays ? " ".repeat(LABEL_GAP) : "";
1858
+ const overlay = hasOverlays ? formatContractNodeOverlays(style, overlays.markers, overlays.refs) : "";
1742
1859
  lines.push(trimTrailingWhitespace(`${gutterPad}${hashText}${overlayPad}${overlay}`));
1743
1860
  continue;
1744
1861
  }
@@ -1746,23 +1863,30 @@ function renderMigrationGraphTree(model, opts) {
1746
1863
  if (edge === void 0) continue;
1747
1864
  const dirNamePadding = " ".repeat(Math.max(0, dirNameWidth - edge.dirName.length));
1748
1865
  const laneIndex = row.laneIndex ?? 0;
1749
- const dirName = `${(opts.colorize && laneIndex > NEUTRAL_LANE ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
1866
+ const dirName = `${(opts.colorize && laneIndex > 0 ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
1750
1867
  const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
1751
1868
  const annotationSuffix = formatEdgeAnnotationSuffix(edge.migrationHash, opts, style);
1752
1869
  lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}${annotationSuffix}`));
1753
1870
  }
1754
1871
  return lines.join("\n");
1755
1872
  }
1873
+ 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;
1879
+ }
1756
1880
  /**
1757
- * A compact key for the `--tree` visual language: the contract marker, the
1758
- * in-lane direction arrows, the empty baseline, the `(refs)` overlay (including
1759
- * the reserved `db` live-database and `contract` working-schema markers), and a
1881
+ * A compact key for the tree visual language: the contract node glyph, the
1882
+ * in-lane direction arrows, the empty baseline, the system-marker `<…>` and
1883
+ * user-ref `(…)` bracket conventions (two illustrative example lines), and a
1760
1884
  * worked sample of the data-column `from → to` migration hash arrow.
1761
1885
  *
1762
1886
  * Honors the same glyph palette (unicode vs ASCII) and `colorize` gate as the
1763
1887
  * tree renderer, so the key matches whatever the graph itself drew and stays
1764
1888
  * pipe-safe (zero ANSI when color is off). The caller adds the trailing blank
1765
- * line that separates this stderr key from the graph on stdout.
1889
+ * line that separates this stderr key from the tree on stdout.
1766
1890
  */
1767
1891
  function renderMigrationGraphLegend(opts) {
1768
1892
  const palette = paletteFor(opts.glyphMode ?? "unicode");
@@ -1771,17 +1895,72 @@ function renderMigrationGraphLegend(opts) {
1771
1895
  const sampleArrow = `${style.sourceHash("aaaaaa")} ${style.glyph(palette.forwardArrow)} ${style.destHash("bbbbbb")}`;
1772
1896
  const statusGlyphs = overlayStatusGlyphs(opts.glyphMode ?? "unicode");
1773
1897
  const appliedPending = opts.colorize ? ` ${green(statusGlyphs.applied)} ${style.summary("applied")} ${yellow(statusGlyphs.pending)} ${style.summary("pending")}` : ` ${statusGlyphs.applied} ${style.summary("applied")} ${statusGlyphs.pending} ${style.summary("pending")}`;
1898
+ const exampleMarkers = formatLegendExampleMarkers(opts.colorize);
1899
+ const exampleRefs = opts.colorize ? style.refs(["prod", "staging"]) : "(prod, staging)";
1774
1900
  return [
1775
1901
  "Legend:",
1776
1902
  ` ${style.kind(node)} ${style.summary("contract")} ${style.kind(palette.edgeArrow.forward)} ${style.summary("forward")} ${style.kind(palette.edgeArrow.rollback)} ${style.summary("rollback")}`,
1777
1903
  ` ${style.kind(palette.edgeArrow.self)} ${style.summary("migration without schema change")}`,
1778
1904
  appliedPending,
1779
1905
  ` ${style.kind(palette.emptySource)} ${style.summary("empty database (baseline)")}`,
1780
- ` ${style.refs(["refs"])} ${style.summary(`${DB_MARKER_NAME} / ${CONTRACT_MARKER_NAME} markers`)}`,
1906
+ ` ${exampleMarkers} ${style.summary("live markers (contract on disk, database state)")}`,
1907
+ ` ${exampleRefs} ${style.summary("user-defined refs")}`,
1781
1908
  ` ${sampleArrow} ${style.summary("migration from contract aaaaaa to bbbbbb")}`
1782
1909
  ].join("\n");
1783
1910
  }
1784
1911
  //#endregion
1785
- export { renderMigrationListWithStyle as a, buildMigrationGraphLayout as c, IDENTITY_MIGRATION_LIST_STYLER as i, renderMigrationGraphTree as n, abbreviateContractHash as o, createAnsiMigrationListStyler as r, buildMigrationGraphRows as s, renderMigrationGraphLegend as t };
1912
+ //#region src/utils/formatters/migration-graph-space-render.ts
1913
+ function mergeMigrationEdgeAnnotations(listOverlay, statusOverlay) {
1914
+ const merged = /* @__PURE__ */ new Map();
1915
+ for (const [migrationHash, listAnnotation] of listOverlay) {
1916
+ const statusAnnotation = statusOverlay.get(migrationHash);
1917
+ merged.set(migrationHash, {
1918
+ ...listAnnotation,
1919
+ ...statusAnnotation?.status !== void 0 ? { status: statusAnnotation.status } : {}
1920
+ });
1921
+ }
1922
+ return merged;
1923
+ }
1924
+ function computeGlobalMaxEdgeTreePrefixWidth(inputs) {
1925
+ let globalMax = 0;
1926
+ for (const input of inputs) {
1927
+ const layout = buildMigrationGraphLayout(buildMigrationGraphRows(input.graph, { contractHash: input.liveContractHash }));
1928
+ globalMax = Math.max(globalMax, computeMaxEdgeTreePrefixWidthForLayout(layout));
1929
+ }
1930
+ return globalMax;
1931
+ }
1932
+ function computeGlobalMaxDirNameWidth(inputs) {
1933
+ let globalMax = 0;
1934
+ for (const input of inputs) {
1935
+ const layout = buildMigrationGraphLayout(buildMigrationGraphRows(input.graph, { contractHash: input.liveContractHash }));
1936
+ globalMax = Math.max(globalMax, computeMaxDirNameLengthForLayout(layout));
1937
+ }
1938
+ return globalMax;
1939
+ }
1940
+ function renderMigrationGraphSpaceTreeInternal(input) {
1941
+ const layout = buildMigrationGraphLayout(buildMigrationGraphRows(input.graph, { contractHash: input.liveContractHash }));
1942
+ const listOverlay = buildEdgeAnnotationsByHashFromListEntries(input.migrations);
1943
+ const edgeAnnotationsByHash = input.statusOverlayByHash === void 0 ? listOverlay : mergeMigrationEdgeAnnotations(listOverlay, input.statusOverlayByHash);
1944
+ return renderMigrationGraphTree(layout, {
1945
+ refsByHash: input.refsByHash ?? buildRefsByHashFromListEntries(input.migrations),
1946
+ contractHash: input.liveContractHash,
1947
+ edgeAnnotationsByHash,
1948
+ colorize: input.colorize,
1949
+ glyphMode: input.glyphMode,
1950
+ ...input.dbHash !== void 0 ? { dbHash: input.dbHash } : {},
1951
+ ...input.styler !== void 0 ? { styler: input.styler } : {},
1952
+ ...input.globalMaxEdgeTreePrefixWidth !== void 0 ? { globalMaxEdgeTreePrefixWidth: input.globalMaxEdgeTreePrefixWidth } : {},
1953
+ ...input.globalMaxDirNameWidth !== void 0 ? { globalMaxDirNameWidth: input.globalMaxDirNameWidth } : {}
1954
+ });
1955
+ }
1956
+ function renderMigrationGraphSpaceTree(input) {
1957
+ return renderMigrationGraphSpaceTreeInternal(input);
1958
+ }
1959
+ function indentMigrationGraphTreeBlock(treeOutput, indent) {
1960
+ if (treeOutput.length === 0) return treeOutput;
1961
+ return treeOutput.split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
1962
+ }
1963
+ //#endregion
1964
+ export { renderMigrationGraphLegend as a, renderMigrationListWithStyle as c, renderMigrationGraphSpaceTree as i, abbreviateContractHash as l, computeGlobalMaxEdgeTreePrefixWidth as n, createAnsiMigrationListStyler as o, indentMigrationGraphTreeBlock as r, IDENTITY_MIGRATION_LIST_STYLER as s, computeGlobalMaxDirNameWidth as t };
1786
1965
 
1787
- //# sourceMappingURL=migration-graph-tree-render-DyDBuJEX.mjs.map
1966
+ //# sourceMappingURL=migration-graph-space-render-dmLLWift.mjs.map