@pipefy/pipefy-process-coder 0.1.0 → 0.1.2

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 (162) hide show
  1. package/.agents/skills/ppc-pipefy-flow-authoring/SKILL.md +262 -262
  2. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-danfe-consulta.json +247 -247
  3. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-2-webhook-retorno-consulta.json +589 -589
  4. package/.agents/skills/ppc-pipefy-flow-authoring/examples/01-main-4-recebimento-barramento.json +1391 -1391
  5. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-2-danfe-retorno.json +623 -623
  6. package/.agents/skills/ppc-pipefy-flow-authoring/examples/02-subflow-4-criacao-operacao.json +636 -636
  7. package/.agents/skills/ppc-pipefy-flow-authoring/examples/03-subflow-4-criacao-titulo.json +3642 -3642
  8. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-cedente.json +863 -863
  9. package/.agents/skills/ppc-pipefy-flow-authoring/examples/04-subflow-4-criacao-sacado.json +799 -799
  10. package/.agents/skills/ppc-pipefy-flow-authoring/examples/README.md +42 -42
  11. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-acompanhamento-cobranca.json +581 -581
  12. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-nfe-monitoramento.json +503 -503
  13. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-01-subflow-1-titulo-retorno-bancario.json +562 -562
  14. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-cedente.json +557 -557
  15. package/.agents/skills/ppc-pipefy-flow-authoring/examples/hml-02-subflow-2-retorno-consulta-sacado.json +609 -609
  16. package/.agents/skills/ppc-pipefy-pipe-authoring/SKILL.md +146 -146
  17. package/.agents/skills/ppc-pipefy-process-design/SKILL.md +127 -127
  18. package/.agents/skills/ppc-pipefy-workspace/SKILL.md +91 -91
  19. package/AGENTS.md +476 -456
  20. package/LICENSE +202 -0
  21. package/NOTICE +11 -0
  22. package/README.md +830 -808
  23. package/bin/pipe.js +25 -4
  24. package/dist/apply/adopt.js +128 -0
  25. package/dist/apply/agentops.js +42 -0
  26. package/dist/apply/compile.js +722 -0
  27. package/dist/apply/execute.js +277 -0
  28. package/dist/apply/flowops.js +293 -0
  29. package/dist/apply/idmap.js +233 -0
  30. package/dist/apply/mutations.js +704 -0
  31. package/dist/apply/registry.js +348 -0
  32. package/dist/apply/types.js +18 -0
  33. package/dist/cli/args.js +79 -0
  34. package/dist/cli.js +196 -0
  35. package/dist/codec/flow.js +164 -0
  36. package/dist/codec/pack.js +88 -0
  37. package/dist/codec/roundtrip.js +86 -0
  38. package/dist/codec/unpack.js +181 -0
  39. package/dist/commands/agents.js +145 -0
  40. package/dist/commands/apply.js +982 -0
  41. package/dist/commands/context.js +79 -0
  42. package/dist/commands/create.js +63 -0
  43. package/dist/commands/diff.js +294 -0
  44. package/dist/commands/flows.js +348 -0
  45. package/dist/commands/misc.js +562 -0
  46. package/dist/commands/plan.js +301 -0
  47. package/dist/commands/pull.js +549 -0
  48. package/dist/commands/runs.js +74 -0
  49. package/dist/commands/skills.js +131 -0
  50. package/dist/commands/verify.js +205 -0
  51. package/dist/config.js +125 -0
  52. package/dist/diff/agents.js +80 -0
  53. package/dist/diff/diff.js +983 -0
  54. package/dist/diff/flow.js +258 -0
  55. package/{src/diff/html.ts → dist/diff/html.js} +274 -322
  56. package/dist/diff/render.js +90 -0
  57. package/dist/model/payload.js +84 -0
  58. package/dist/model/tree.js +7 -0
  59. package/dist/model/volatile.js +54 -0
  60. package/dist/pipefy/agents.js +131 -0
  61. package/dist/pipefy/automations.js +155 -0
  62. package/dist/pipefy/capability.js +84 -0
  63. package/dist/pipefy/client.js +204 -0
  64. package/dist/pipefy/discovery.js +148 -0
  65. package/dist/pipefy/internal.js +406 -0
  66. package/dist/pipefy/ipaas.js +269 -0
  67. package/dist/pipefy/reconstruct.js +676 -0
  68. package/dist/pipefy/reference.js +172 -0
  69. package/dist/pipefy/snapshot.js +185 -0
  70. package/dist/pipefy/toolkit.js +174 -0
  71. package/{src → dist}/pipefy/toolkit_bearer.py +60 -14
  72. package/dist/report/integrations.js +165 -0
  73. package/dist/report/run.js +356 -0
  74. package/dist/util/fsx.js +50 -0
  75. package/dist/util/git.js +25 -0
  76. package/dist/util/json.js +56 -0
  77. package/dist/util/log.js +69 -0
  78. package/dist/util/pool.js +42 -0
  79. package/dist/util/slug.js +23 -0
  80. package/dist/util/tui.js +287 -0
  81. package/dist/validate/index.js +86 -0
  82. package/dist/validate/integrity.js +370 -0
  83. package/dist/validate/reference.js +120 -0
  84. package/dist/validate/schema.js +278 -0
  85. package/dist/version.js +18 -0
  86. package/dist/workspace/agents.js +245 -0
  87. package/{src/workspace/docs.ts → dist/workspace/docs.js} +367 -407
  88. package/dist/workspace/flows.js +149 -0
  89. package/dist/workspace/layout.js +146 -0
  90. package/dist/workspace/lock.js +83 -0
  91. package/dist/workspace/read.js +168 -0
  92. package/dist/workspace/reference.js +24 -0
  93. package/dist/workspace/stamp.js +234 -0
  94. package/dist/workspace/write.js +194 -0
  95. package/package.json +38 -35
  96. package/src/apply/adopt.ts +0 -150
  97. package/src/apply/agentops.ts +0 -71
  98. package/src/apply/compile.ts +0 -875
  99. package/src/apply/execute.ts +0 -336
  100. package/src/apply/flowops.ts +0 -399
  101. package/src/apply/idmap.ts +0 -241
  102. package/src/apply/mutations.ts +0 -955
  103. package/src/apply/registry.ts +0 -430
  104. package/src/apply/types.ts +0 -134
  105. package/src/cli/args.ts +0 -88
  106. package/src/cli.ts +0 -211
  107. package/src/codec/flow.ts +0 -199
  108. package/src/codec/pack.ts +0 -103
  109. package/src/codec/roundtrip.ts +0 -94
  110. package/src/codec/unpack.ts +0 -198
  111. package/src/commands/agents.ts +0 -184
  112. package/src/commands/apply.ts +0 -1144
  113. package/src/commands/context.ts +0 -119
  114. package/src/commands/create.ts +0 -79
  115. package/src/commands/diff.ts +0 -314
  116. package/src/commands/flows.ts +0 -414
  117. package/src/commands/misc.ts +0 -644
  118. package/src/commands/plan.ts +0 -331
  119. package/src/commands/pull.ts +0 -567
  120. package/src/commands/runs.ts +0 -83
  121. package/src/commands/skills.ts +0 -137
  122. package/src/commands/verify.ts +0 -253
  123. package/src/config.ts +0 -168
  124. package/src/diff/agents.ts +0 -122
  125. package/src/diff/diff.ts +0 -1130
  126. package/src/diff/flow.ts +0 -318
  127. package/src/diff/render.ts +0 -101
  128. package/src/model/payload.ts +0 -154
  129. package/src/model/tree.ts +0 -99
  130. package/src/model/volatile.ts +0 -55
  131. package/src/pipefy/agents.ts +0 -165
  132. package/src/pipefy/automations.ts +0 -219
  133. package/src/pipefy/capability.ts +0 -119
  134. package/src/pipefy/client.ts +0 -267
  135. package/src/pipefy/discovery.ts +0 -209
  136. package/src/pipefy/internal.ts +0 -380
  137. package/src/pipefy/ipaas.ts +0 -365
  138. package/src/pipefy/reconstruct.ts +0 -775
  139. package/src/pipefy/reference.ts +0 -251
  140. package/src/pipefy/snapshot.ts +0 -245
  141. package/src/pipefy/toolkit.ts +0 -200
  142. package/src/report/integrations.ts +0 -231
  143. package/src/report/run.ts +0 -475
  144. package/src/util/fsx.ts +0 -45
  145. package/src/util/git.ts +0 -32
  146. package/src/util/json.ts +0 -55
  147. package/src/util/log.ts +0 -76
  148. package/src/util/pool.ts +0 -48
  149. package/src/util/slug.ts +0 -26
  150. package/src/util/tui.ts +0 -335
  151. package/src/validate/index.ts +0 -123
  152. package/src/validate/integrity.ts +0 -387
  153. package/src/validate/reference.ts +0 -136
  154. package/src/validate/schema.ts +0 -328
  155. package/src/workspace/agents.ts +0 -290
  156. package/src/workspace/flows.ts +0 -191
  157. package/src/workspace/layout.ts +0 -165
  158. package/src/workspace/lock.ts +0 -148
  159. package/src/workspace/read.ts +0 -165
  160. package/src/workspace/reference.ts +0 -24
  161. package/src/workspace/stamp.ts +0 -301
  162. package/src/workspace/write.ts +0 -225
@@ -1,322 +1,274 @@
1
- import type { ChangeSet } from './diff.ts';
2
- import type { Plan } from '../apply/types.ts';
3
-
4
- /**
5
- * A self-contained HTML diff view.
6
- *
7
- * The one thing a GUI genuinely buys is *seeing* a diff, so that is what this is:
8
- * one file, no dependencies, no build, opens in any browser. It is the reason the
9
- * desktop shell of PLAN.md phase 3 was dropped rather than built.
10
- */
11
-
12
- export type ViewModel = {
13
- changeSet: ChangeSet;
14
- plan?: Plan | null;
15
- workspace?: string;
16
- generatedAt: string;
17
- };
18
-
19
- const escapeHtml = (s: string) =>
20
- s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
21
-
22
- const STYLE = `
23
- :root {
24
- color-scheme: light dark;
25
- --bg: #fbfbfa; --panel: #ffffff; --border: #e4e2dd; --text: #1c1b19;
26
- --muted: #6f6b64; --add: #1a7f4b; --add-bg: #e8f5ee; --del: #b3261e;
27
- --del-bg: #fdeceb; --upd: #8a6100; --upd-bg: #fdf3e2; --move: #6b4ea8;
28
- --move-bg: #f1ecfa; --accent: #1f6feb; --code: #f4f3f0;
29
- }
30
- @media (prefers-color-scheme: dark) {
31
- :root {
32
- --bg: #171716; --panel: #1f1f1e; --border: #33322f; --text: #ecebe8;
33
- --muted: #9b968d; --add: #56d38a; --add-bg: #14301f; --del: #ff8a80;
34
- --del-bg: #331a18; --upd: #e5b567; --upd-bg: #32270f; --move: #b39ddb;
35
- --move-bg: #241d33; --accent: #6ea8ff; --code: #262624;
36
- }
37
- }
38
- * { box-sizing: border-box; }
39
- body {
40
- margin: 0; background: var(--bg); color: var(--text);
41
- font: 14px/1.5 ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
42
- }
43
- code, pre, .mono { font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace; }
44
- header {
45
- position: sticky; top: 0; z-index: 5; background: var(--panel);
46
- border-bottom: 1px solid var(--border); padding: 14px 20px;
47
- }
48
- h1 { font-size: 16px; margin: 0 0 4px; font-weight: 650; }
49
- .sub { color: var(--muted); font-size: 12.5px; }
50
- .counts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
51
- .pill {
52
- border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px;
53
- font-size: 12px; background: var(--bg); cursor: pointer; color: var(--text);
54
- }
55
- .pill[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
56
- .pill.create { color: var(--add); } .pill.delete { color: var(--del); }
57
- .pill.update { color: var(--upd); } .pill.reorder { color: var(--move); }
58
- main { padding: 18px 20px 60px; max-width: 1100px; margin: 0 auto; }
59
- input[type=search] {
60
- width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
61
- background: var(--bg); color: var(--text); margin-top: 10px; font-size: 13px;
62
- }
63
- .notice {
64
- border: 1px solid var(--border); border-left: 3px solid var(--upd); background: var(--upd-bg);
65
- padding: 10px 12px; border-radius: 8px; margin-bottom: 16px; font-size: 13px;
66
- }
67
- .notice.block { border-left-color: var(--del); background: var(--del-bg); }
68
- .notice h3 { margin: 0 0 6px; font-size: 13px; }
69
- .notice ul { margin: 4px 0 0 18px; padding: 0; }
70
- .entity { margin: 22px 0 8px; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
71
- .change {
72
- border: 1px solid var(--border); border-radius: 10px; background: var(--panel);
73
- margin-bottom: 8px; overflow: hidden;
74
- }
75
- .change > summary {
76
- padding: 10px 12px; cursor: pointer; display: flex; gap: 10px; align-items: baseline;
77
- list-style: none;
78
- }
79
- .change > summary::-webkit-details-marker { display: none; }
80
- .op {
81
- flex: none; width: 20px; height: 20px; border-radius: 6px; display: grid; place-items: center;
82
- font-weight: 700; font-size: 13px;
83
- }
84
- .op.create { color: var(--add); background: var(--add-bg); }
85
- .op.delete { color: var(--del); background: var(--del-bg); }
86
- .op.update, .op.archive, .op.unarchive { color: var(--upd); background: var(--upd-bg); }
87
- .op.reorder { color: var(--move); background: var(--move-bg); }
88
- .title { font-weight: 550; }
89
- .flags { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
90
- .flag {
91
- font-size: 11px; border: 1px solid var(--border); border-radius: 999px;
92
- padding: 1px 8px; color: var(--muted);
93
- }
94
- .flag.warn { color: var(--del); border-color: var(--del); }
95
- .body { border-top: 1px solid var(--border); padding: 10px 12px; }
96
- table.deltas { width: 100%; border-collapse: collapse; font-size: 12.5px; }
97
- table.deltas td { vertical-align: top; padding: 5px 6px; border-top: 1px solid var(--border); }
98
- table.deltas tr:first-child td { border-top: 0; }
99
- td.k { width: 22%; color: var(--muted); }
100
- td.b, td.a { width: 39%; }
101
- .val { display: block; padding: 3px 6px; border-radius: 5px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
102
- .val.before { background: var(--del-bg); color: var(--del); }
103
- .val.after { background: var(--add-bg); color: var(--add); }
104
- pre.json {
105
- background: var(--code); border-radius: 8px; padding: 10px; overflow-x: auto;
106
- font-size: 12px; max-height: 340px; margin: 8px 0 0;
107
- }
108
- .step { font-size: 12.5px; color: var(--muted); margin-top: 8px; }
109
- .step b { color: var(--text); }
110
- .empty { color: var(--muted); padding: 40px 0; text-align: center; }
111
- footer { color: var(--muted); font-size: 12px; padding: 0 20px 30px; max-width: 1100px; margin: 0 auto; }
112
- `;
113
-
114
- const CLIENT = `
115
- const state = { ops: new Set(), entities: new Set(), q: '' };
116
- function apply() {
117
- const q = state.q.toLowerCase();
118
- let shown = 0;
119
- document.querySelectorAll('.change').forEach(function (el) {
120
- const op = el.dataset.op, ent = el.dataset.entity, hay = el.dataset.search;
121
- let ok = true;
122
- if (state.ops.size && !state.ops.has(op)) ok = false;
123
- if (ok && state.entities.size && !state.entities.has(ent)) ok = false;
124
- if (ok && q && hay.indexOf(q) === -1) ok = false;
125
- el.hidden = !ok;
126
- if (ok) shown++;
127
- });
128
- document.querySelectorAll('.entity').forEach(function (h) {
129
- const any = document.querySelectorAll('.change[data-entity="' + h.dataset.entity + '"]:not([hidden])').length;
130
- h.hidden = any === 0;
131
- });
132
- const empty = document.getElementById('empty');
133
- if (empty) empty.hidden = shown !== 0;
134
- }
135
- document.querySelectorAll('.pill[data-op]').forEach(function (b) {
136
- b.addEventListener('click', function () {
137
- const op = b.dataset.op;
138
- if (state.ops.has(op)) { state.ops.delete(op); b.setAttribute('aria-pressed', 'false'); }
139
- else { state.ops.add(op); b.setAttribute('aria-pressed', 'true'); }
140
- apply();
141
- });
142
- });
143
- document.querySelectorAll('.pill[data-entity]').forEach(function (b) {
144
- b.addEventListener('click', function () {
145
- const e = b.dataset.entity;
146
- if (state.entities.has(e)) { state.entities.delete(e); b.setAttribute('aria-pressed', 'false'); }
147
- else { state.entities.add(e); b.setAttribute('aria-pressed', 'true'); }
148
- apply();
149
- });
150
- });
151
- const search = document.getElementById('q');
152
- if (search) search.addEventListener('input', function () { state.q = search.value; apply(); });
153
- document.addEventListener('keydown', function (e) {
154
- if (e.key === 'e' && (e.metaKey || e.ctrlKey)) {
155
- e.preventDefault();
156
- const open = document.querySelectorAll('.change[open]').length === 0;
157
- document.querySelectorAll('.change').forEach(function (d) { d.open = open; });
158
- }
159
- });
160
- `;
161
-
162
- const val = (v: unknown): string => {
163
- if (v === null || v === undefined) return 'null';
164
- if (typeof v === 'string') return v;
165
- return JSON.stringify(v, null, 2);
166
- };
167
-
168
- export const renderHtml = (vm: ViewModel): string => {
169
- const cs = vm.changeSet;
170
- const plan = vm.plan ?? null;
171
-
172
- const stepFor = (key: string) => plan?.steps.filter((s) => s.changeKey === key) ?? [];
173
- const supportFor = (key: string) => plan?.classifications.find((c) => c.changeKey === key);
174
-
175
- const opCounts = cs.changes.reduce<Record<string, number>>((a, c) => {
176
- a[c.op] = (a[c.op] ?? 0) + 1;
177
- return a;
178
- }, {});
179
- const entities = [...new Set(cs.changes.map((c) => c.entity))].sort();
180
-
181
- const blocked = plan?.classifications.filter((c) => c.status === 'UNSUPPORTED') ?? [];
182
-
183
- const byEntity = new Map<string, typeof cs.changes>();
184
- for (const c of cs.changes) {
185
- const arr = byEntity.get(c.entity) ?? [];
186
- arr.push(c);
187
- byEntity.set(c.entity, arr);
188
- }
189
-
190
- const changeHtml = [...byEntity.entries()]
191
- .map(([entity, group]) => {
192
- const rows = group
193
- .map((c) => {
194
- const sup = supportFor(c.key);
195
- const steps = stepFor(c.key);
196
- const search = [c.entity, c.label, c.op, ...c.flags, ...c.deltas.map((d) => d.key)]
197
- .join(' ')
198
- .toLowerCase();
199
-
200
- const deltaRows = c.deltas
201
- .map(
202
- (d) => `<tr>
203
- <td class="k mono">${escapeHtml(d.key)}</td>
204
- <td class="b"><span class="val before mono">${escapeHtml(val(d.before))}</span></td>
205
- <td class="a"><span class="val after mono">${escapeHtml(val(d.after))}</span></td>
206
- </tr>`,
207
- )
208
- .join('');
209
-
210
- const full =
211
- c.op === 'create'
212
- ? `<pre class="json mono">${escapeHtml(JSON.stringify(c.after, null, 2))}</pre>`
213
- : c.op === 'delete'
214
- ? `<pre class="json mono">${escapeHtml(JSON.stringify(c.before, null, 2))}</pre>`
215
- : '';
216
-
217
- const stepHtml = steps.length
218
- ? `<div class="step">${steps
219
- .map((s) => `<b>${escapeHtml(s.kind)}</b> ${escapeHtml(s.description)}`)
220
- .join('<br>')}</div>`
221
- : '';
222
-
223
- const supHtml = sup
224
- ? `<div class="step"><b>${sup.status}</b>${sup.via ? ` via <span class="mono">${escapeHtml(sup.via)}</span>` : ''}${
225
- sup.reason ? ` — ${escapeHtml(sup.reason)}` : ''
226
- }</div>`
227
- : '';
228
-
229
- const flagHtml = [
230
- ...c.flags.map(
231
- (f) =>
232
- `<span class="flag${/destructive|readonly-edit|field-type-change|field-phase-move|phase-reorder|start-form/.test(f) ? ' warn' : ''}">${escapeHtml(f)}</span>`,
233
- ),
234
- sup && sup.status !== 'SUPPORTED' ? `<span class="flag warn">${sup.status}</span>` : '',
235
- ]
236
- .filter(Boolean)
237
- .join('');
238
-
239
- return `<details class="change" data-op="${c.op}" data-entity="${escapeHtml(c.entity)}" data-search="${escapeHtml(search)}">
240
- <summary>
241
- <span class="op ${c.op}">${c.op === 'create' ? '+' : c.op === 'delete' ? '−' : c.op === 'reorder' ? '↕' : '~'}</span>
242
- <span class="title">${escapeHtml(c.label)}</span>
243
- <span class="flags">${flagHtml}</span>
244
- </summary>
245
- <div class="body">
246
- ${deltaRows ? `<table class="deltas">${deltaRows}</table>` : ''}
247
- ${full}
248
- ${supHtml}
249
- ${stepHtml}
250
- </div>
251
- </details>`;
252
- })
253
- .join('\n');
254
- return `<h2 class="entity" data-entity="${escapeHtml(entity)}">${escapeHtml(entity)} · ${group.length}</h2>\n${rows}`;
255
- })
256
- .join('\n');
257
-
258
- const noticeBlocked = blocked.length
259
- ? `<div class="notice block">
260
- <h3>${blocked.length} change${blocked.length === 1 ? '' : 's'} cannot be applied</h3>
261
- <ul>${blocked
262
- .map(
263
- (b) => `<li><span class="mono">${escapeHtml(b.entity)}.${escapeHtml(b.op)}</span> — ${escapeHtml(b.reason ?? 'no write path')}</li>`,
264
- )
265
- .join('')}</ul>
266
- </div>`
267
- : '';
268
-
269
- const noticeSkipped = cs.skipped.length
270
- ? `<div class="notice">
271
- <h3>${cs.skipped.length} entity group${cs.skipped.length === 1 ? '' : 's'} not diffed</h3>
272
- <ul>${cs.skipped
273
- .map((s) => `<li><span class="mono">${escapeHtml(s.entity)}</span> — ${escapeHtml(s.reason)}</li>`)
274
- .join('')}</ul>
275
- </div>`
276
- : '';
277
-
278
- const opPills = Object.entries(opCounts)
279
- .map(([op, n]) => `<button class="pill ${op}" data-op="${op}" aria-pressed="false">${op} ${n}</button>`)
280
- .join('');
281
- const entityPills = entities
282
- .map((e) => `<button class="pill" data-entity="${escapeHtml(e)}" aria-pressed="false">${escapeHtml(e)}</button>`)
283
- .join('');
284
-
285
- const header =
286
- cs.mode === 'cross-repo'
287
- ? `${escapeHtml(cs.from.name)} <span class="mono">${cs.from.repoId}</span> &rarr; ${escapeHtml(cs.to.name)} <span class="mono">${cs.to.repoId}</span>`
288
- : `${escapeHtml(cs.to.name)} <span class="mono">${cs.to.repoId}</span>`;
289
-
290
- return `<!doctype html>
291
- <html lang="en">
292
- <head>
293
- <meta charset="utf-8">
294
- <meta name="viewport" content="width=device-width, initial-scale=1">
295
- <title>pipe diff — ${escapeHtml(cs.to.name)}</title>
296
- <style>${STYLE}</style>
297
- </head>
298
- <body>
299
- <header>
300
- <h1>${header}</h1>
301
- <div class="sub">
302
- ${cs.mode} diff &middot; baseline from ${escapeHtml(cs.from.source)} &middot; compared tree from ${escapeHtml(cs.to.source)}
303
- ${vm.workspace ? ` &middot; <span class="mono">${escapeHtml(vm.workspace)}</span>` : ''}
304
- </div>
305
- <div class="counts">${opPills}${entityPills}</div>
306
- <input id="q" type="search" placeholder="filter by name, field or flag — cmd/ctrl+E toggles all">
307
- </header>
308
- <main>
309
- ${noticeBlocked}
310
- ${noticeSkipped}
311
- ${cs.changes.length ? changeHtml : '<div class="empty">No changes. The edited workspace matches the baseline.</div>'}
312
- <div class="empty" id="empty" hidden>Nothing matches this filter.</div>
313
- </main>
314
- <footer>
315
- Generated ${escapeHtml(vm.generatedAt)} by <span class="mono">pipe diff --html</span>.
316
- Webhook URLs and automation credentials can appear in these values — this file is as sensitive as the workspace.
317
- </footer>
318
- <script>${CLIENT}</script>
319
- </body>
320
- </html>
321
- `;
322
- };
1
+ const escapeHtml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2
+ const STYLE = `
3
+ :root {
4
+ color-scheme: light dark;
5
+ --bg: #fbfbfa; --panel: #ffffff; --border: #e4e2dd; --text: #1c1b19;
6
+ --muted: #6f6b64; --add: #1a7f4b; --add-bg: #e8f5ee; --del: #b3261e;
7
+ --del-bg: #fdeceb; --upd: #8a6100; --upd-bg: #fdf3e2; --move: #6b4ea8;
8
+ --move-bg: #f1ecfa; --accent: #1f6feb; --code: #f4f3f0;
9
+ }
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --bg: #171716; --panel: #1f1f1e; --border: #33322f; --text: #ecebe8;
13
+ --muted: #9b968d; --add: #56d38a; --add-bg: #14301f; --del: #ff8a80;
14
+ --del-bg: #331a18; --upd: #e5b567; --upd-bg: #32270f; --move: #b39ddb;
15
+ --move-bg: #241d33; --accent: #6ea8ff; --code: #262624;
16
+ }
17
+ }
18
+ * { box-sizing: border-box; }
19
+ body {
20
+ margin: 0; background: var(--bg); color: var(--text);
21
+ font: 14px/1.5 ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
22
+ }
23
+ code, pre, .mono { font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace; }
24
+ header {
25
+ position: sticky; top: 0; z-index: 5; background: var(--panel);
26
+ border-bottom: 1px solid var(--border); padding: 14px 20px;
27
+ }
28
+ h1 { font-size: 16px; margin: 0 0 4px; font-weight: 650; }
29
+ .sub { color: var(--muted); font-size: 12.5px; }
30
+ .counts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
31
+ .pill {
32
+ border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px;
33
+ font-size: 12px; background: var(--bg); cursor: pointer; color: var(--text);
34
+ }
35
+ .pill[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
36
+ .pill.create { color: var(--add); } .pill.delete { color: var(--del); }
37
+ .pill.update { color: var(--upd); } .pill.reorder { color: var(--move); }
38
+ main { padding: 18px 20px 60px; max-width: 1100px; margin: 0 auto; }
39
+ input[type=search] {
40
+ width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px;
41
+ background: var(--bg); color: var(--text); margin-top: 10px; font-size: 13px;
42
+ }
43
+ .notice {
44
+ border: 1px solid var(--border); border-left: 3px solid var(--upd); background: var(--upd-bg);
45
+ padding: 10px 12px; border-radius: 8px; margin-bottom: 16px; font-size: 13px;
46
+ }
47
+ .notice.block { border-left-color: var(--del); background: var(--del-bg); }
48
+ .notice h3 { margin: 0 0 6px; font-size: 13px; }
49
+ .notice ul { margin: 4px 0 0 18px; padding: 0; }
50
+ .entity { margin: 22px 0 8px; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
51
+ .change {
52
+ border: 1px solid var(--border); border-radius: 10px; background: var(--panel);
53
+ margin-bottom: 8px; overflow: hidden;
54
+ }
55
+ .change > summary {
56
+ padding: 10px 12px; cursor: pointer; display: flex; gap: 10px; align-items: baseline;
57
+ list-style: none;
58
+ }
59
+ .change > summary::-webkit-details-marker { display: none; }
60
+ .op {
61
+ flex: none; width: 20px; height: 20px; border-radius: 6px; display: grid; place-items: center;
62
+ font-weight: 700; font-size: 13px;
63
+ }
64
+ .op.create { color: var(--add); background: var(--add-bg); }
65
+ .op.delete { color: var(--del); background: var(--del-bg); }
66
+ .op.update, .op.archive, .op.unarchive { color: var(--upd); background: var(--upd-bg); }
67
+ .op.reorder { color: var(--move); background: var(--move-bg); }
68
+ .title { font-weight: 550; }
69
+ .flags { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
70
+ .flag {
71
+ font-size: 11px; border: 1px solid var(--border); border-radius: 999px;
72
+ padding: 1px 8px; color: var(--muted);
73
+ }
74
+ .flag.warn { color: var(--del); border-color: var(--del); }
75
+ .body { border-top: 1px solid var(--border); padding: 10px 12px; }
76
+ table.deltas { width: 100%; border-collapse: collapse; font-size: 12.5px; }
77
+ table.deltas td { vertical-align: top; padding: 5px 6px; border-top: 1px solid var(--border); }
78
+ table.deltas tr:first-child td { border-top: 0; }
79
+ td.k { width: 22%; color: var(--muted); }
80
+ td.b, td.a { width: 39%; }
81
+ .val { display: block; padding: 3px 6px; border-radius: 5px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
82
+ .val.before { background: var(--del-bg); color: var(--del); }
83
+ .val.after { background: var(--add-bg); color: var(--add); }
84
+ pre.json {
85
+ background: var(--code); border-radius: 8px; padding: 10px; overflow-x: auto;
86
+ font-size: 12px; max-height: 340px; margin: 8px 0 0;
87
+ }
88
+ .step { font-size: 12.5px; color: var(--muted); margin-top: 8px; }
89
+ .step b { color: var(--text); }
90
+ .empty { color: var(--muted); padding: 40px 0; text-align: center; }
91
+ footer { color: var(--muted); font-size: 12px; padding: 0 20px 30px; max-width: 1100px; margin: 0 auto; }
92
+ `;
93
+ const CLIENT = `
94
+ const state = { ops: new Set(), entities: new Set(), q: '' };
95
+ function apply() {
96
+ const q = state.q.toLowerCase();
97
+ let shown = 0;
98
+ document.querySelectorAll('.change').forEach(function (el) {
99
+ const op = el.dataset.op, ent = el.dataset.entity, hay = el.dataset.search;
100
+ let ok = true;
101
+ if (state.ops.size && !state.ops.has(op)) ok = false;
102
+ if (ok && state.entities.size && !state.entities.has(ent)) ok = false;
103
+ if (ok && q && hay.indexOf(q) === -1) ok = false;
104
+ el.hidden = !ok;
105
+ if (ok) shown++;
106
+ });
107
+ document.querySelectorAll('.entity').forEach(function (h) {
108
+ const any = document.querySelectorAll('.change[data-entity="' + h.dataset.entity + '"]:not([hidden])').length;
109
+ h.hidden = any === 0;
110
+ });
111
+ const empty = document.getElementById('empty');
112
+ if (empty) empty.hidden = shown !== 0;
113
+ }
114
+ document.querySelectorAll('.pill[data-op]').forEach(function (b) {
115
+ b.addEventListener('click', function () {
116
+ const op = b.dataset.op;
117
+ if (state.ops.has(op)) { state.ops.delete(op); b.setAttribute('aria-pressed', 'false'); }
118
+ else { state.ops.add(op); b.setAttribute('aria-pressed', 'true'); }
119
+ apply();
120
+ });
121
+ });
122
+ document.querySelectorAll('.pill[data-entity]').forEach(function (b) {
123
+ b.addEventListener('click', function () {
124
+ const e = b.dataset.entity;
125
+ if (state.entities.has(e)) { state.entities.delete(e); b.setAttribute('aria-pressed', 'false'); }
126
+ else { state.entities.add(e); b.setAttribute('aria-pressed', 'true'); }
127
+ apply();
128
+ });
129
+ });
130
+ const search = document.getElementById('q');
131
+ if (search) search.addEventListener('input', function () { state.q = search.value; apply(); });
132
+ document.addEventListener('keydown', function (e) {
133
+ if (e.key === 'e' && (e.metaKey || e.ctrlKey)) {
134
+ e.preventDefault();
135
+ const open = document.querySelectorAll('.change[open]').length === 0;
136
+ document.querySelectorAll('.change').forEach(function (d) { d.open = open; });
137
+ }
138
+ });
139
+ `;
140
+ const val = (v) => {
141
+ if (v === null || v === undefined)
142
+ return 'null';
143
+ if (typeof v === 'string')
144
+ return v;
145
+ return JSON.stringify(v, null, 2);
146
+ };
147
+ export const renderHtml = (vm) => {
148
+ const cs = vm.changeSet;
149
+ const plan = vm.plan ?? null;
150
+ const stepFor = (key) => plan?.steps.filter((s) => s.changeKey === key) ?? [];
151
+ const supportFor = (key) => plan?.classifications.find((c) => c.changeKey === key);
152
+ const opCounts = cs.changes.reduce((a, c) => {
153
+ a[c.op] = (a[c.op] ?? 0) + 1;
154
+ return a;
155
+ }, {});
156
+ const entities = [...new Set(cs.changes.map((c) => c.entity))].sort();
157
+ const blocked = plan?.classifications.filter((c) => c.status === 'UNSUPPORTED') ?? [];
158
+ const byEntity = new Map();
159
+ for (const c of cs.changes) {
160
+ const arr = byEntity.get(c.entity) ?? [];
161
+ arr.push(c);
162
+ byEntity.set(c.entity, arr);
163
+ }
164
+ const changeHtml = [...byEntity.entries()]
165
+ .map(([entity, group]) => {
166
+ const rows = group
167
+ .map((c) => {
168
+ const sup = supportFor(c.key);
169
+ const steps = stepFor(c.key);
170
+ const search = [c.entity, c.label, c.op, ...c.flags, ...c.deltas.map((d) => d.key)]
171
+ .join(' ')
172
+ .toLowerCase();
173
+ const deltaRows = c.deltas
174
+ .map((d) => `<tr>
175
+ <td class="k mono">${escapeHtml(d.key)}</td>
176
+ <td class="b"><span class="val before mono">${escapeHtml(val(d.before))}</span></td>
177
+ <td class="a"><span class="val after mono">${escapeHtml(val(d.after))}</span></td>
178
+ </tr>`)
179
+ .join('');
180
+ const full = c.op === 'create'
181
+ ? `<pre class="json mono">${escapeHtml(JSON.stringify(c.after, null, 2))}</pre>`
182
+ : c.op === 'delete'
183
+ ? `<pre class="json mono">${escapeHtml(JSON.stringify(c.before, null, 2))}</pre>`
184
+ : '';
185
+ const stepHtml = steps.length
186
+ ? `<div class="step">${steps
187
+ .map((s) => `<b>${escapeHtml(s.kind)}</b> ${escapeHtml(s.description)}`)
188
+ .join('<br>')}</div>`
189
+ : '';
190
+ const supHtml = sup
191
+ ? `<div class="step"><b>${sup.status}</b>${sup.via ? ` via <span class="mono">${escapeHtml(sup.via)}</span>` : ''}${sup.reason ? ` — ${escapeHtml(sup.reason)}` : ''}</div>`
192
+ : '';
193
+ const flagHtml = [
194
+ ...c.flags.map((f) => `<span class="flag${/destructive|readonly-edit|field-type-change|field-phase-move|phase-reorder|start-form/.test(f) ? ' warn' : ''}">${escapeHtml(f)}</span>`),
195
+ sup && sup.status !== 'SUPPORTED' ? `<span class="flag warn">${sup.status}</span>` : '',
196
+ ]
197
+ .filter(Boolean)
198
+ .join('');
199
+ return `<details class="change" data-op="${c.op}" data-entity="${escapeHtml(c.entity)}" data-search="${escapeHtml(search)}">
200
+ <summary>
201
+ <span class="op ${c.op}">${c.op === 'create' ? '+' : c.op === 'delete' ? '−' : c.op === 'reorder' ? '↕' : '~'}</span>
202
+ <span class="title">${escapeHtml(c.label)}</span>
203
+ <span class="flags">${flagHtml}</span>
204
+ </summary>
205
+ <div class="body">
206
+ ${deltaRows ? `<table class="deltas">${deltaRows}</table>` : ''}
207
+ ${full}
208
+ ${supHtml}
209
+ ${stepHtml}
210
+ </div>
211
+ </details>`;
212
+ })
213
+ .join('\n');
214
+ return `<h2 class="entity" data-entity="${escapeHtml(entity)}">${escapeHtml(entity)} · ${group.length}</h2>\n${rows}`;
215
+ })
216
+ .join('\n');
217
+ const noticeBlocked = blocked.length
218
+ ? `<div class="notice block">
219
+ <h3>${blocked.length} change${blocked.length === 1 ? '' : 's'} cannot be applied</h3>
220
+ <ul>${blocked
221
+ .map((b) => `<li><span class="mono">${escapeHtml(b.entity)}.${escapeHtml(b.op)}</span> — ${escapeHtml(b.reason ?? 'no write path')}</li>`)
222
+ .join('')}</ul>
223
+ </div>`
224
+ : '';
225
+ const noticeSkipped = cs.skipped.length
226
+ ? `<div class="notice">
227
+ <h3>${cs.skipped.length} entity group${cs.skipped.length === 1 ? '' : 's'} not diffed</h3>
228
+ <ul>${cs.skipped
229
+ .map((s) => `<li><span class="mono">${escapeHtml(s.entity)}</span> — ${escapeHtml(s.reason)}</li>`)
230
+ .join('')}</ul>
231
+ </div>`
232
+ : '';
233
+ const opPills = Object.entries(opCounts)
234
+ .map(([op, n]) => `<button class="pill ${op}" data-op="${op}" aria-pressed="false">${op} ${n}</button>`)
235
+ .join('');
236
+ const entityPills = entities
237
+ .map((e) => `<button class="pill" data-entity="${escapeHtml(e)}" aria-pressed="false">${escapeHtml(e)}</button>`)
238
+ .join('');
239
+ const header = cs.mode === 'cross-repo'
240
+ ? `${escapeHtml(cs.from.name)} <span class="mono">${cs.from.repoId}</span> &rarr; ${escapeHtml(cs.to.name)} <span class="mono">${cs.to.repoId}</span>`
241
+ : `${escapeHtml(cs.to.name)} <span class="mono">${cs.to.repoId}</span>`;
242
+ return `<!doctype html>
243
+ <html lang="en">
244
+ <head>
245
+ <meta charset="utf-8">
246
+ <meta name="viewport" content="width=device-width, initial-scale=1">
247
+ <title>pipe diff — ${escapeHtml(cs.to.name)}</title>
248
+ <style>${STYLE}</style>
249
+ </head>
250
+ <body>
251
+ <header>
252
+ <h1>${header}</h1>
253
+ <div class="sub">
254
+ ${cs.mode} diff &middot; baseline from ${escapeHtml(cs.from.source)} &middot; compared tree from ${escapeHtml(cs.to.source)}
255
+ ${vm.workspace ? ` &middot; <span class="mono">${escapeHtml(vm.workspace)}</span>` : ''}
256
+ </div>
257
+ <div class="counts">${opPills}${entityPills}</div>
258
+ <input id="q" type="search" placeholder="filter by name, field or flag — cmd/ctrl+E toggles all">
259
+ </header>
260
+ <main>
261
+ ${noticeBlocked}
262
+ ${noticeSkipped}
263
+ ${cs.changes.length ? changeHtml : '<div class="empty">No changes. The edited workspace matches the baseline.</div>'}
264
+ <div class="empty" id="empty" hidden>Nothing matches this filter.</div>
265
+ </main>
266
+ <footer>
267
+ Generated ${escapeHtml(vm.generatedAt)} by <span class="mono">pipe diff --html</span>.
268
+ Webhook URLs and automation credentials can appear in these values — this file is as sensitive as the workspace.
269
+ </footer>
270
+ <script>${CLIENT}</script>
271
+ </body>
272
+ </html>
273
+ `;
274
+ };