@tailor-platform/sdk 1.68.0 → 1.70.0

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 (117) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/dist/application-BakHtldG.mjs +4 -0
  3. package/dist/application-Df5_I83n.mjs +6432 -0
  4. package/dist/application-Df5_I83n.mjs.map +1 -0
  5. package/dist/cli/erd-viewer-assets/app.js +279 -36
  6. package/dist/cli/erd-viewer-assets/index.html +4 -0
  7. package/dist/cli/erd-viewer-assets/styles.css +252 -5
  8. package/dist/cli/index.mjs +650 -98
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/lib.d.mts +247 -160
  11. package/dist/cli/lib.mjs +3 -3
  12. package/dist/cli/lib.mjs.map +1 -1
  13. package/dist/cli/skills.mjs +1 -1
  14. package/dist/completion/zsh-worker.zsh +175 -24
  15. package/dist/configure/index.d.mts +5 -5
  16. package/dist/configure/index.mjs +12 -6
  17. package/dist/configure/index.mjs.map +1 -1
  18. package/dist/{crashreport-u9y2npiy.mjs → crashreport-BqyvFk-_.mjs} +2 -2
  19. package/dist/{crashreport-u9y2npiy.mjs.map → crashreport-BqyvFk-_.mjs.map} +1 -1
  20. package/dist/{crashreport-6jpCceOF.mjs → crashreport-BwF8cHF0.mjs} +1 -1
  21. package/dist/enum-constants-C7DaWeQo.mjs.map +1 -1
  22. package/dist/field-C4zdJLW5.mjs.map +1 -1
  23. package/dist/file-utils-BHPxPXmn.mjs.map +1 -1
  24. package/dist/{idp-BlBPtXJ-.d.mts → idp-BmYwCXnJ.d.mts} +30 -3
  25. package/dist/{idp-BZPqpcYY.mjs → idp-ynUfzwpz.mjs} +9 -1
  26. package/dist/idp-ynUfzwpz.mjs.map +1 -0
  27. package/dist/{index-DvEUb3pX.d.mts → index-BAEaAqmz.d.mts} +112 -53
  28. package/dist/{index-CklcVeMG.d.mts → index-C-vsbx27.d.mts} +2 -2
  29. package/dist/{index-hXoO-AOC.d.mts → index-CKI0eZP6.d.mts} +2 -2
  30. package/dist/{index-DYhnxXYR.d.mts → index-CrqOgUF2.d.mts} +2 -2
  31. package/dist/{index-DlDRSzFZ.d.mts → index-DESLU9kI.d.mts} +2 -2
  32. package/dist/{index-DRhMpdnA.d.mts → index-dKNk8hjo.d.mts} +2 -2
  33. package/dist/job-BpsFXPbi.mjs.map +1 -1
  34. package/dist/{kysely-type-D1e0Vwkd.mjs → kysely-type-CSoZxVKN.mjs} +2 -2
  35. package/dist/{kysely-type-D1e0Vwkd.mjs.map → kysely-type-CSoZxVKN.mjs.map} +1 -1
  36. package/dist/{logger-DpJyJvNz.mjs → logger-DKF-JsAK.mjs} +3 -3
  37. package/dist/{logger-DpJyJvNz.mjs.map → logger-DKF-JsAK.mjs.map} +1 -1
  38. package/dist/{mock-DMgIygjE.mjs → mock-wf5qeZLi.mjs} +19 -9
  39. package/dist/mock-wf5qeZLi.mjs.map +1 -0
  40. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  41. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  42. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  43. package/dist/plugin/builtin/kysely-type/index.mjs +1 -1
  44. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  45. package/dist/plugin/index.d.mts +1 -1
  46. package/dist/plugin/index.mjs.map +1 -1
  47. package/dist/registry-D0uB0OrK.mjs.map +1 -1
  48. package/dist/{repl-editor-CJG3sz7A.mjs → repl-editor-DD5YP5mt.mjs} +4 -4
  49. package/dist/{repl-editor-CJG3sz7A.mjs.map → repl-editor-DD5YP5mt.mjs.map} +1 -1
  50. package/dist/runtime/globals.d.mts +3 -2
  51. package/dist/runtime/idp.d.mts +2 -2
  52. package/dist/runtime/idp.mjs +1 -1
  53. package/dist/runtime/index.d.mts +2 -2
  54. package/dist/runtime/index.mjs +1 -1
  55. package/dist/{runtime-DxaBq6U8.mjs → runtime-CSY0eD4_.mjs} +411 -221
  56. package/dist/runtime-CSY0eD4_.mjs.map +1 -0
  57. package/dist/{schema-1msIhXwA.mjs → schema-C4fkpWV_.mjs} +9 -15
  58. package/dist/schema-C4fkpWV_.mjs.map +1 -0
  59. package/dist/seed-YAbtMy65.mjs.map +1 -1
  60. package/dist/{service-wI3Hvrgx.mjs → service-B2Jd9CxS.mjs} +2 -2
  61. package/dist/service-B2Jd9CxS.mjs.map +1 -0
  62. package/dist/service-CRaa4Joe.mjs +4 -0
  63. package/dist/{service-DMohAx8a.mjs → service-DDWgZL_L2.mjs} +2 -2
  64. package/dist/service-DDWgZL_L2.mjs.map +1 -0
  65. package/dist/service_pb-DGSmn-aF.mjs +4 -0
  66. package/dist/{application-WpWwTyk9.mjs → service_pb-DSNjrcbW.mjs} +22 -6176
  67. package/dist/service_pb-DSNjrcbW.mjs.map +1 -0
  68. package/dist/telemetry-BQbbVo2t.mjs.map +1 -1
  69. package/dist/{types-2Be3wSMc.mjs → types-32lUMToj.mjs} +1 -1
  70. package/dist/{types-CmzfQP_m.mjs → types-D4QMmNWh.mjs} +1 -12
  71. package/dist/types-D4QMmNWh.mjs.map +1 -0
  72. package/dist/{types-Bzr0RQME.d.mts → types-Dynq4AJv.d.mts} +2 -2
  73. package/dist/{types-DZrtN6-H.d.mts → types-rj8YJcEe.d.mts} +5 -2
  74. package/dist/utils/test/index.d.mts +2 -2
  75. package/dist/utils/test/index.mjs.map +1 -1
  76. package/dist/vitest/environment.mjs +1 -1
  77. package/dist/vitest/environment.mjs.map +1 -1
  78. package/dist/vitest/index.mjs +4 -4
  79. package/dist/vitest/index.mjs.map +1 -1
  80. package/dist/vitest/setup.mjs +1 -1
  81. package/dist/{workflow.generated-1S50BhEb.d.mts → workflow.generated-DJULCuRr.d.mts} +274 -174
  82. package/docs/cli/application.md +39 -201
  83. package/docs/cli/auth.md +12 -256
  84. package/docs/cli/completion.md +0 -24
  85. package/docs/cli/crashreport.md +0 -58
  86. package/docs/cli/executor.md +2 -166
  87. package/docs/cli/function.md +2 -118
  88. package/docs/cli/organization.md +1 -211
  89. package/docs/cli/query.md +0 -20
  90. package/docs/cli/secret.md +70 -250
  91. package/docs/cli/setup.md +2 -41
  92. package/docs/cli/skills.md +0 -39
  93. package/docs/cli/staticwebsite.md +24 -172
  94. package/docs/cli/tailordb.md +25 -251
  95. package/docs/cli/upgrade.md +0 -20
  96. package/docs/cli/user.md +41 -246
  97. package/docs/cli/workflow.md +30 -189
  98. package/docs/cli/workspace.md +164 -537
  99. package/docs/cli-reference.md +61 -37
  100. package/docs/configuration.md +7 -1
  101. package/docs/github-actions.md +27 -0
  102. package/docs/multi-environment.md +22 -0
  103. package/docs/services/aigateway.md +4 -2
  104. package/docs/services/http-adapter.md +16 -1
  105. package/docs/services/idp.md +55 -2
  106. package/docs/services/staticwebsite.md +7 -1
  107. package/package.json +23 -18
  108. package/dist/application-Djeezk3m.mjs +0 -4
  109. package/dist/application-WpWwTyk9.mjs.map +0 -1
  110. package/dist/idp-BZPqpcYY.mjs.map +0 -1
  111. package/dist/mock-DMgIygjE.mjs.map +0 -1
  112. package/dist/runtime-DxaBq6U8.mjs.map +0 -1
  113. package/dist/schema-1msIhXwA.mjs.map +0 -1
  114. package/dist/service-BHQIerYh.mjs +0 -4
  115. package/dist/service-DMohAx8a.mjs.map +0 -1
  116. package/dist/service-wI3Hvrgx.mjs.map +0 -1
  117. package/dist/types-CmzfQP_m.mjs.map +0 -1
@@ -13,6 +13,7 @@ const FIT_PADDING = 80;
13
13
  const MIN_ZOOM = 0.25;
14
14
  const MAX_ZOOM = 2.2;
15
15
  const DEFAULT_SHOW_MODE = "TABLE_NAME";
16
+ const DEFAULT_VIEW_MODE = "diff";
16
17
  const SHOW_MODE_OPTIONS = [
17
18
  { value: "ALL_FIELDS", label: "All Fields" },
18
19
  { value: "TABLE_NAME", label: "Table Name" },
@@ -40,10 +41,17 @@ const elements = {
40
41
  fitView: document.getElementById("fit-view"),
41
42
  showMode: document.getElementById("show-mode"),
42
43
  showModeMenu: document.getElementById("show-mode-menu"),
44
+ viewModeControl: document.getElementById("view-mode-control"),
45
+ viewModeCurrent: document.getElementById("view-mode-current"),
46
+ viewModeDiff: document.getElementById("view-mode-diff"),
43
47
  copyLink: document.getElementById("copy-link"),
44
48
  };
45
49
 
46
50
  let schema;
51
+ let diffViewSchema;
52
+ let currentViewSchema;
53
+ let diffMetadata;
54
+ let erdDiff;
47
55
  let layout;
48
56
  let selectedTable;
49
57
  let searchText = "";
@@ -56,8 +64,29 @@ let activeCardDrag;
56
64
  let activeCanvasPan;
57
65
  let activeViewportAnimation;
58
66
  let suppressNextCanvasClick = false;
67
+ let viewMode = DEFAULT_VIEW_MODE;
59
68
  const manualNodePositions = new Map();
60
69
  const hiddenTableNames = new Set();
70
+ let diffIndex = new Map();
71
+ let tableDiffIndex = new Map();
72
+
73
+ const DIFF_LABELS = {
74
+ added: "Added",
75
+ changed: "Changed",
76
+ removed: "Removed",
77
+ };
78
+
79
+ const DIFF_MARKS = {
80
+ added: "+",
81
+ changed: "~",
82
+ removed: "-",
83
+ };
84
+
85
+ const DIFF_ACTION_RANK = {
86
+ added: 2,
87
+ changed: 1,
88
+ removed: 3,
89
+ };
61
90
 
62
91
  function escapeHtml(value) {
63
92
  return String(value ?? "")
@@ -75,6 +104,102 @@ function tableByName(name) {
75
104
  return schema?.tables.find((table) => table.name === name);
76
105
  }
77
106
 
107
+ function strongestDiffAction(current, next) {
108
+ if (!current) return next;
109
+ if (!next) return current;
110
+ return DIFF_ACTION_RANK[next] > DIFF_ACTION_RANK[current] ? next : current;
111
+ }
112
+
113
+ function diffKey(entity, path) {
114
+ return `${entity}:${path}`;
115
+ }
116
+
117
+ function diffChange(entity, path) {
118
+ return diffIndex.get(diffKey(entity, path));
119
+ }
120
+
121
+ function setTableDiffAction(tableName, action) {
122
+ if (!tableName || !action) return;
123
+ tableDiffIndex.set(tableName, strongestDiffAction(tableDiffIndex.get(tableName), action));
124
+ }
125
+
126
+ function tableNameFromEntityPath(path) {
127
+ return path.split(".")[0];
128
+ }
129
+
130
+ function tableNamesFromRelationChange(change) {
131
+ const relation = schema?.relations.find((item) => item.name === change.path);
132
+ if (relation) return [relation.sourceTable, relation.targetTable];
133
+
134
+ const [source, target] = change.path.split("->");
135
+ return [tableNameFromEntityPath(source || ""), tableNameFromEntityPath(target || "")].filter(
136
+ Boolean,
137
+ );
138
+ }
139
+
140
+ function setDiff(nextDiff) {
141
+ erdDiff = nextDiff;
142
+ diffIndex = new Map();
143
+ tableDiffIndex = new Map();
144
+ if (!nextDiff?.changes) return;
145
+
146
+ for (const change of nextDiff.changes) {
147
+ diffIndex.set(diffKey(change.entity, change.path), change);
148
+ if (change.entity === "table") {
149
+ setTableDiffAction(change.path, change.action);
150
+ continue;
151
+ }
152
+ if (change.entity === "column" || change.entity === "index") {
153
+ setTableDiffAction(tableNameFromEntityPath(change.path), "changed");
154
+ continue;
155
+ }
156
+ if (change.entity === "relation") {
157
+ for (const tableName of tableNamesFromRelationChange(change)) {
158
+ setTableDiffAction(tableName, "changed");
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ function diffClass(action) {
165
+ return action ? `is-diff-${action}` : "";
166
+ }
167
+
168
+ function diffBadge(action, detail) {
169
+ if (!action) return "";
170
+ const title = detail ? `${DIFF_LABELS[action]}: ${detail}` : DIFF_LABELS[action];
171
+ return `<span class="diff-badge diff-badge-${action}" title="${escapeHtml(title)}">${DIFF_MARKS[action]}</span>`;
172
+ }
173
+
174
+ function diffDetail(change) {
175
+ if (!change?.detail) return "";
176
+ return `<span class="diff-detail">${escapeHtml(change.detail)}</span>`;
177
+ }
178
+
179
+ function tableDiffChange(tableName) {
180
+ return diffChange("table", tableName);
181
+ }
182
+
183
+ function tableDiffAction(tableName) {
184
+ return tableDiffIndex.get(tableName);
185
+ }
186
+
187
+ function columnDiffChange(tableName, columnName) {
188
+ return diffChange("column", `${tableName}.${columnName}`);
189
+ }
190
+
191
+ function indexDiffChange(tableName, indexName) {
192
+ return diffChange("index", `${tableName}.${indexName}`);
193
+ }
194
+
195
+ function relationDiffChange(relation) {
196
+ return diffChange("relation", relation.name);
197
+ }
198
+
199
+ function relationDiffAction(relation) {
200
+ return relationDiffChange(relation)?.action;
201
+ }
202
+
78
203
  function showModeOption(value) {
79
204
  return SHOW_MODE_OPTIONS.find((option) => option.value === value);
80
205
  }
@@ -98,6 +223,10 @@ function readHashState() {
98
223
  if (showModeOption(nextShowMode)) {
99
224
  showMode = nextShowMode;
100
225
  }
226
+ const nextViewMode = params.get("view");
227
+ if (nextViewMode === "current" || nextViewMode === "diff") {
228
+ viewMode = nextViewMode;
229
+ }
101
230
  hiddenTableNames.clear();
102
231
  for (const tableName of (params.get("hidden") || "").split(",")) {
103
232
  if (tableName) hiddenTableNames.add(tableName);
@@ -117,6 +246,9 @@ function writeHashState() {
117
246
  if (hiddenTableNames.size > 0) {
118
247
  params.set("hidden", [...hiddenTableNames].toSorted((a, b) => a.localeCompare(b)).join(","));
119
248
  }
249
+ if (canSwitchViewMode() && viewMode !== DEFAULT_VIEW_MODE) {
250
+ params.set("view", viewMode);
251
+ }
120
252
  params.set("z", viewport.z.toFixed(3));
121
253
  try {
122
254
  history.replaceState(null, "", `#${params.toString()}`);
@@ -130,11 +262,9 @@ function writeHashState() {
130
262
  const SCHEMA_BLOCK_PATTERN =
131
263
  /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
132
264
 
133
- // Read the schema from the embedded `#erd-schema` data block in the current
134
- // document (the viewer is always a self-contained HTML).
135
- function embeddedSchema() {
265
+ function embeddedJson(id) {
136
266
  if (typeof document === "undefined") return undefined;
137
- const element = document.getElementById("erd-schema");
267
+ const element = document.getElementById(id);
138
268
  if (!element) return undefined;
139
269
  try {
140
270
  return JSON.parse(element.textContent);
@@ -143,6 +273,20 @@ function embeddedSchema() {
143
273
  }
144
274
  }
145
275
 
276
+ // Read the schema from the embedded `#erd-schema` data block in the current
277
+ // document (the viewer is always a self-contained HTML).
278
+ function embeddedSchema() {
279
+ return embeddedJson("erd-schema");
280
+ }
281
+
282
+ function embeddedDiff() {
283
+ return embeddedJson("erd-diff");
284
+ }
285
+
286
+ function embeddedCurrentSchema() {
287
+ return embeddedJson("erd-current-schema");
288
+ }
289
+
146
290
  // Re-fetch the served HTML and extract its embedded schema. Used by watch mode
147
291
  // to pick up rebuilds without a separate schema.json.
148
292
  async function fetchServedSchema() {
@@ -297,8 +441,10 @@ function eyeIcon(hidden) {
297
441
  }
298
442
 
299
443
  function renderHeader() {
300
- elements.namespace.textContent = schema.namespace;
301
- elements.revision.textContent = `${schema.tables.length} tables / ${schema.relations.length} relations / ${schema.revision}`;
444
+ elements.namespace.textContent = erdDiff ? `${schema.namespace} diff` : schema.namespace;
445
+ elements.revision.textContent = erdDiff
446
+ ? `${schema.tables.length} tables / ${schema.relations.length} relations / +${erdDiff.summary.added} ~${erdDiff.summary.changed} -${erdDiff.summary.removed} / ${erdDiff.baseRevision} -> ${erdDiff.headRevision}`
447
+ : `${schema.tables.length} tables / ${schema.relations.length} relations / ${schema.revision}`;
302
448
  const visibleCount = visibleTables().length;
303
449
  elements.tableSummary.textContent =
304
450
  visibleCount === schema.tables.length
@@ -318,8 +464,10 @@ function renderTableList() {
318
464
  .map((table) => {
319
465
  const hidden = isTableHidden(table.name);
320
466
  const visibilityLabel = hidden ? "Show" : "Hide";
467
+ const change = tableDiffChange(table.name);
468
+ const action = tableDiffAction(table.name);
321
469
  return `
322
- <div class="table-list-row ${hidden ? "is-hidden" : ""}">
470
+ <div class="table-list-row ${hidden ? "is-hidden" : ""} ${diffClass(action)}">
323
471
  <button
324
472
  type="button"
325
473
  class="table-select"
@@ -327,7 +475,10 @@ function renderTableList() {
327
475
  aria-current="${table.name === selectedTable}"
328
476
  >
329
477
  <span class="table-list-icon" aria-hidden="true"></span>
330
- <span>${escapeHtml(table.name)}</span>
478
+ <span class="table-list-label">
479
+ <span class="table-list-name">${escapeHtml(table.name)}</span>
480
+ ${diffBadge(action, change?.detail)}
481
+ </span>
331
482
  </button>
332
483
  <button
333
484
  type="button"
@@ -363,14 +514,19 @@ function tableFieldRows(table) {
363
514
  return `
364
515
  <div class="table-fields">
365
516
  ${columns
366
- .map(
367
- (column) => `
368
- <div class="table-field ${isKeyColumn(column) ? "is-key" : ""}">
369
- <span class="field-name">${escapeHtml(column.name)}</span>
517
+ .map((column) => {
518
+ const change = columnDiffChange(table.name, column.name);
519
+ const action = change?.action;
520
+ return `
521
+ <div class="table-field ${isKeyColumn(column) ? "is-key" : ""} ${diffClass(action)}">
522
+ <span class="field-name">
523
+ <span class="field-name-text">${escapeHtml(column.name)}</span>
524
+ ${diffBadge(action, change?.detail)}
525
+ </span>
370
526
  <span class="field-type">${escapeHtml(column.type)}${column.array ? "[]" : ""}</span>
371
527
  </div>
372
- `,
373
- )
528
+ `;
529
+ })
374
530
  .join("")}
375
531
  </div>
376
532
  `;
@@ -383,10 +539,12 @@ function renderNodes() {
383
539
  const node = layout.nodes.get(table.name);
384
540
  const related = isTableRelatedToSelection(table.name);
385
541
  const muted = !matchesSearch(table) || !related;
542
+ const change = tableDiffChange(table.name);
543
+ const action = tableDiffAction(table.name);
386
544
  return `
387
545
  <button
388
546
  type="button"
389
- class="table-card ${table.name === selectedTable ? "is-selected" : ""} ${related ? "is-related" : ""} ${muted ? "is-muted" : ""}"
547
+ class="table-card ${table.name === selectedTable ? "is-selected" : ""} ${related ? "is-related" : ""} ${muted ? "is-muted" : ""} ${diffClass(action)}"
390
548
  data-table="${escapeHtml(table.name)}"
391
549
  aria-label="Focus table ${escapeHtml(table.name)}"
392
550
  aria-pressed="${table.name === selectedTable}"
@@ -394,7 +552,10 @@ function renderNodes() {
394
552
  >
395
553
  <div class="table-head">
396
554
  <span class="table-icon" aria-hidden="true"></span>
397
- <div class="table-name">${escapeHtml(table.name)}</div>
555
+ <div class="table-name-wrap">
556
+ <div class="table-name">${escapeHtml(table.name)}</div>
557
+ ${diffBadge(action, change?.detail)}
558
+ </div>
398
559
  </div>
399
560
  ${tableFieldRows(table)}
400
561
  </button>
@@ -547,7 +708,7 @@ function oneMarker(x, y) {
547
708
  `;
548
709
  }
549
710
 
550
- function cardinalityMarker(cardinality, point, sideSign, selected) {
711
+ function cardinalityMarker(cardinality, point, sideSign, selected, action) {
551
712
  const crowFootTip = CROW_FOOT_TIP_OFFSET;
552
713
  const crowFootJoin = CROW_FOOT_JOIN_OFFSET;
553
714
  const outer = CARDINALITY_OUTER_OFFSET;
@@ -578,7 +739,7 @@ function cardinalityMarker(cardinality, point, sideSign, selected) {
578
739
  }
579
740
 
580
741
  return `
581
- <g class="edge-cardinality ${selected ? "is-selected" : ""}">
742
+ <g class="edge-cardinality ${selected ? "is-selected" : ""} ${diffClass(action)}">
582
743
  ${parts.join("")}
583
744
  </g>
584
745
  `;
@@ -599,10 +760,11 @@ function renderEdges() {
599
760
  const geometry = edgeGeometry(source, target);
600
761
  const cardinality = relationCardinality(relation);
601
762
  const direction = geometry.sourceRight ? 1 : -1;
763
+ const action = relationDiffAction(relation);
602
764
  return `
603
- <path class="edge ${selected ? "is-selected" : ""}" d="${geometry.d}"></path>
604
- ${cardinalityMarker(cardinality.source, geometry.sourceNodePoint, direction, selected)}
605
- ${cardinalityMarker(cardinality.target, geometry.targetNodePoint, -direction, selected)}
765
+ <path class="edge ${selected ? "is-selected" : ""} ${diffClass(action)}" d="${geometry.d}"></path>
766
+ ${cardinalityMarker(cardinality.source, geometry.sourceNodePoint, direction, selected, action)}
767
+ ${cardinalityMarker(cardinality.target, geometry.targetNodePoint, -direction, selected, action)}
606
768
  `;
607
769
  })
608
770
  .join("");
@@ -621,7 +783,18 @@ function relationRows(table, direction) {
621
783
  direction === "out"
622
784
  ? `${relation.sourceColumns.join(", ")} -> ${relation.targetTable}.${relation.targetColumns.join(", ")}`
623
785
  : `${relation.sourceTable}.${relation.sourceColumns.join(", ")} -> ${relation.targetColumns.join(", ")}`;
624
- return `<div class="detail-row"><strong>${escapeHtml(label)}</strong><code>${escapeHtml(relation.kind)}</code></div>`;
786
+ const change = relationDiffChange(relation);
787
+ const action = change?.action;
788
+ return `<div class="detail-row ${diffClass(action)}">
789
+ <div>
790
+ <span class="detail-name">
791
+ <strong>${escapeHtml(label)}</strong>
792
+ ${diffBadge(action, change?.detail)}
793
+ </span>
794
+ ${diffDetail(change)}
795
+ </div>
796
+ <code>${escapeHtml(relation.kind)}</code>
797
+ </div>`;
625
798
  })
626
799
  .join("")}
627
800
  </div>
@@ -654,13 +827,22 @@ function renderDetails() {
654
827
  return;
655
828
  }
656
829
 
830
+ const tableChange = tableDiffChange(table.name);
831
+ const tableAction = tableDiffAction(table.name);
657
832
  elements.details.hidden = false;
658
833
  elements.main.classList.remove("is-details-collapsed");
659
834
  elements.details.innerHTML = `
660
835
  <div class="details-inner">
661
836
  <section>
662
- <h2><span class="table-icon" aria-hidden="true"></span>${escapeHtml(table.name)}</h2>
837
+ <h2>
838
+ <span class="table-icon" aria-hidden="true"></span>
839
+ <span class="detail-name">
840
+ <span>${escapeHtml(table.name)}</span>
841
+ ${diffBadge(tableAction, tableChange?.detail)}
842
+ </span>
843
+ </h2>
663
844
  <p>${escapeHtml(table.description || table.pluralForm)}</p>
845
+ ${diffDetail(tableChange)}
664
846
  </section>
665
847
  <section class="details-section">
666
848
  <h3>Outgoing Relations</h3>
@@ -674,18 +856,24 @@ function renderDetails() {
674
856
  <h3>Columns</h3>
675
857
  <div class="detail-list">
676
858
  ${table.columns
677
- .map(
678
- (column) => `
679
- <div class="detail-row">
859
+ .map((column) => {
860
+ const change = columnDiffChange(table.name, column.name);
861
+ const action = change?.action;
862
+ return `
863
+ <div class="detail-row ${diffClass(action)}">
680
864
  <div>
681
- <strong>${escapeHtml(column.name)}</strong>
865
+ <span class="detail-name">
866
+ <strong>${escapeHtml(column.name)}</strong>
867
+ ${diffBadge(action, change?.detail)}
868
+ </span>
869
+ ${diffDetail(change)}
682
870
  ${column.description ? `<p>${escapeHtml(column.description)}</p>` : ""}
683
871
  ${columnPills(column)}
684
872
  </div>
685
873
  <code>${escapeHtml(column.type)}${column.array ? "[]" : ""}</code>
686
874
  </div>
687
- `,
688
- )
875
+ `;
876
+ })
689
877
  .join("")}
690
878
  </div>
691
879
  </section>
@@ -695,14 +883,22 @@ function renderDetails() {
695
883
  <h3>Indexes</h3>
696
884
  <div class="detail-list">
697
885
  ${table.indexes
698
- .map(
699
- (index) => `
700
- <div class="detail-row">
701
- <strong>${escapeHtml(index.name)}</strong>
886
+ .map((index) => {
887
+ const change = indexDiffChange(table.name, index.name);
888
+ const action = change?.action;
889
+ return `
890
+ <div class="detail-row ${diffClass(action)}">
891
+ <div>
892
+ <span class="detail-name">
893
+ <strong>${escapeHtml(index.name)}</strong>
894
+ ${diffBadge(action, change?.detail)}
895
+ </span>
896
+ ${diffDetail(change)}
897
+ </div>
702
898
  <code>${escapeHtml(index.fields.join(", "))}${index.unique ? " unique" : ""}</code>
703
899
  </div>
704
- `,
705
- )
900
+ `;
901
+ })
706
902
  .join("")}
707
903
  </div>
708
904
  </section>`
@@ -760,6 +956,43 @@ function renderShowModeControls() {
760
956
  });
761
957
  }
762
958
 
959
+ function canSwitchViewMode() {
960
+ return Boolean(diffViewSchema && currentViewSchema && diffMetadata);
961
+ }
962
+
963
+ function renderViewModeControls() {
964
+ const enabled = canSwitchViewMode();
965
+ elements.viewModeControl.hidden = !enabled;
966
+ if (!enabled) return;
967
+
968
+ elements.viewModeCurrent.setAttribute("aria-pressed", String(viewMode === "current"));
969
+ elements.viewModeDiff.setAttribute("aria-pressed", String(viewMode === "diff"));
970
+ }
971
+
972
+ function activateViewMode(nextViewMode) {
973
+ if (nextViewMode === "current" && canSwitchViewMode()) {
974
+ viewMode = "current";
975
+ schema = currentViewSchema;
976
+ setDiff(undefined);
977
+ return;
978
+ }
979
+
980
+ viewMode = DEFAULT_VIEW_MODE;
981
+ schema = diffViewSchema;
982
+ setDiff(diffMetadata);
983
+ }
984
+
985
+ function setViewMode(nextViewMode) {
986
+ if (!canSwitchViewMode() || nextViewMode === viewMode) return;
987
+
988
+ activateViewMode(nextViewMode);
989
+ if (selectedTable && !tableByName(selectedTable)) {
990
+ selectedTable = undefined;
991
+ }
992
+ clearSelectionIfHidden();
993
+ renderAll({ center: false });
994
+ }
995
+
763
996
  function setShowModeMenuOpen(open) {
764
997
  elements.showMode.setAttribute("aria-expanded", String(open));
765
998
  elements.showModeMenu.hidden = !open;
@@ -932,6 +1165,7 @@ function renderAll(options = {}) {
932
1165
  renderHeader();
933
1166
  renderTableList();
934
1167
  renderShowModeControls();
1168
+ renderViewModeControls();
935
1169
  renderEdges();
936
1170
  renderNodes();
937
1171
  renderDetails();
@@ -1071,6 +1305,8 @@ function wireInteractions() {
1071
1305
  elements.showMode.addEventListener("click", () => {
1072
1306
  setShowModeMenuOpen(elements.showModeMenu.hidden);
1073
1307
  });
1308
+ elements.viewModeCurrent.addEventListener("click", () => setViewMode("current"));
1309
+ elements.viewModeDiff.addEventListener("click", () => setViewMode("diff"));
1074
1310
  elements.copyLink.addEventListener("click", async () => {
1075
1311
  try {
1076
1312
  await navigator.clipboard.writeText(location.href);
@@ -1146,6 +1382,10 @@ function startPolling() {
1146
1382
  const nextSchema = await fetchServedSchema();
1147
1383
  if (nextSchema.revision !== schema.revision) {
1148
1384
  schema = nextSchema;
1385
+ diffViewSchema = nextSchema;
1386
+ currentViewSchema = undefined;
1387
+ diffMetadata = undefined;
1388
+ setDiff(undefined);
1149
1389
  if (selectedTable && !tableByName(selectedTable)) {
1150
1390
  selectedTable = undefined;
1151
1391
  }
@@ -1163,7 +1403,10 @@ async function main() {
1163
1403
  readHashState();
1164
1404
  wireInteractions();
1165
1405
  try {
1166
- schema = embeddedSchema() ?? (await fetchServedSchema());
1406
+ diffViewSchema = embeddedSchema() ?? (await fetchServedSchema());
1407
+ currentViewSchema = embeddedCurrentSchema();
1408
+ diffMetadata = embeddedDiff();
1409
+ activateViewMode(viewMode);
1167
1410
  if (selectedTable && !tableByName(selectedTable)) {
1168
1411
  selectedTable = undefined;
1169
1412
  }
@@ -20,6 +20,10 @@
20
20
  <input id="search" type="search" autocomplete="off" placeholder="Search" />
21
21
  </div>
22
22
  <div class="toolbar-actions">
23
+ <div id="view-mode-control" class="view-mode-control" aria-label="ERD view mode" hidden>
24
+ <button id="view-mode-current" class="view-mode-button" type="button">Current</button>
25
+ <button id="view-mode-diff" class="view-mode-button" type="button">Diff</button>
26
+ </div>
23
27
  <button id="copy-link" class="primary-action" type="button">Copy Link</button>
24
28
  </div>
25
29
  </header>