@mevdragon/vidfarm-devcli 0.2.8 → 0.2.10

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 (98) hide show
  1. package/.env.example +9 -0
  2. package/GETTING_STARTED.developers.md +1 -26
  3. package/README.md +3 -0
  4. package/SKILL.developer.md +419 -8
  5. package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +475 -0
  6. package/auto-create-templates/extractor-system-prompt.md +113 -0
  7. package/auto-create-templates/production-graph.schema.json +276 -0
  8. package/auto-create-templates/runbook.md +140 -0
  9. package/auto-create-templates/template-plan.schema.json +230 -0
  10. package/auto-create-templates/template-planner-prompt.md +75 -0
  11. package/dist/src/account-pages-legacy.js +9064 -0
  12. package/dist/src/account-pages.js +41 -620
  13. package/dist/src/app.js +6627 -244
  14. package/dist/src/cli.js +386 -26
  15. package/dist/src/config.js +12 -2
  16. package/dist/src/context.js +35 -35
  17. package/dist/src/db.js +1016 -53
  18. package/dist/src/dev-app-legacy.js +693 -0
  19. package/dist/src/dev-app.js +4 -904
  20. package/dist/src/domain.js +1 -1
  21. package/dist/src/editor-chat-history.js +72 -0
  22. package/dist/src/editor-chat.js +202 -0
  23. package/dist/src/frontend/flockposter-cache-store.js +116 -0
  24. package/dist/src/frontend/homepage-client.js +114 -0
  25. package/dist/src/frontend/homepage-shared.js +3 -0
  26. package/dist/src/frontend/homepage-store.js +27 -0
  27. package/dist/src/frontend/homepage-view.js +147 -0
  28. package/dist/src/frontend/page-runtime-client.js +100 -0
  29. package/dist/src/frontend/page-runtime-store.js +8 -0
  30. package/dist/src/frontend/template-editor-chat.js +2261 -0
  31. package/dist/src/homepage.js +695 -371
  32. package/dist/src/lib/template-style-options.js +95 -15
  33. package/dist/src/page-runtime.js +1 -0
  34. package/dist/src/page-shell.js +941 -0
  35. package/dist/src/primitive-context.js +163 -0
  36. package/dist/src/primitive-registry.js +340 -0
  37. package/dist/src/primitive-sdk.js +3 -0
  38. package/dist/src/primitives/remotion/html-image.js +28 -0
  39. package/dist/src/react-page-shell.js +34 -0
  40. package/dist/src/ready-post-schedule-component.js +1514 -0
  41. package/dist/src/registry.js +36 -17
  42. package/dist/src/runtime.js +6 -1
  43. package/dist/src/services/api-call-history.js +245 -0
  44. package/dist/src/services/auth.js +25 -3
  45. package/dist/src/services/billing.js +248 -9
  46. package/dist/src/services/chat-threads.js +88 -0
  47. package/dist/src/services/job-logs.js +14 -7
  48. package/dist/src/services/jobs.js +13 -2
  49. package/dist/src/services/providers.js +552 -133
  50. package/dist/src/services/rate-limits.js +236 -0
  51. package/dist/src/services/remotion.js +143 -16
  52. package/dist/src/services/storage.js +10 -8
  53. package/dist/src/services/template-certification.js +11 -2
  54. package/dist/src/services/template-loader.js +3 -1
  55. package/dist/src/services/template-sources.js +68 -3
  56. package/dist/src/template-editor-pages.js +2309 -0
  57. package/dist/src/template-editor-shell.js +1507 -0
  58. package/dist/src/worker.js +120 -22
  59. package/package.json +27 -4
  60. package/public/assets/homepage-app.js +54 -0
  61. package/public/assets/homepage-client-app.js +54 -0
  62. package/public/assets/page-runtime-client-app.js +68 -0
  63. package/dist/templates/vidfarm_template_0000/src/lib/images.js +0 -202
  64. package/dist/templates/vidfarm_template_0000/src/remotion/Root.js +0 -34
  65. package/dist/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
  66. package/dist/templates/vidfarm_template_0000/src/sdk.js +0 -3
  67. package/dist/templates/vidfarm_template_0000/src/style-options.js +0 -51
  68. package/dist/templates/vidfarm_template_0000/src/template-dna.js +0 -9
  69. package/dist/templates/vidfarm_template_0000/src/template.js +0 -1447
  70. package/templates/vidfarm_template_0000/README.md +0 -100
  71. package/templates/vidfarm_template_0000/SKILL.md +0 -225
  72. package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
  73. package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
  74. package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
  75. package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
  76. package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
  77. package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
  78. package/templates/vidfarm_template_0000/composition.json +0 -11
  79. package/templates/vidfarm_template_0000/package.json +0 -27
  80. package/templates/vidfarm_template_0000/research/preview/.gitkeep +0 -1
  81. package/templates/vidfarm_template_0000/research/source_notes.md +0 -7
  82. package/templates/vidfarm_template_0000/src/lib/images.js +0 -202
  83. package/templates/vidfarm_template_0000/src/lib/images.ts +0 -241
  84. package/templates/vidfarm_template_0000/src/remotion/Root.js +0 -33
  85. package/templates/vidfarm_template_0000/src/remotion/Root.tsx +0 -75
  86. package/templates/vidfarm_template_0000/src/remotion/index.js +0 -3
  87. package/templates/vidfarm_template_0000/src/remotion/index.tsx +0 -4
  88. package/templates/vidfarm_template_0000/src/sdk.js +0 -3
  89. package/templates/vidfarm_template_0000/src/sdk.ts +0 -140
  90. package/templates/vidfarm_template_0000/src/style-options.js +0 -51
  91. package/templates/vidfarm_template_0000/src/style-options.ts +0 -60
  92. package/templates/vidfarm_template_0000/src/template-dna.js +0 -9
  93. package/templates/vidfarm_template_0000/src/template-dna.ts +0 -15
  94. package/templates/vidfarm_template_0000/src/template.js +0 -1447
  95. package/templates/vidfarm_template_0000/src/template.ts +0 -2049
  96. package/templates/vidfarm_template_0000/template.config.json +0 -21
  97. package/templates/vidfarm_template_0000/tmp/solobacterium-moorei-slideshow.request.json +0 -31
  98. package/templates/vidfarm_template_0000/tsconfig.json +0 -19
@@ -0,0 +1,2309 @@
1
+ import { escapeAttribute, escapeHtml, renderPageShell, withAccountQuery } from "./page-shell.js";
2
+ import { renderTemplateEditorChrome, TEMPLATE_EDITOR_SHELL_SCRIPT, TEMPLATE_EDITOR_SHELL_STYLES } from "./template-editor-shell.js";
3
+ export function renderTemplateEditorDocsPage(input) {
4
+ const backHref = withAccountQuery("/discover", input.account.userId);
5
+ const hasHistoryAccess = input.account.isLoggedIn;
6
+ const preview = input.template.previewUrl
7
+ ? renderPreview(input.template.previewUrl, `${input.template.title} preview`)
8
+ : `<div class="editor-preview editor-preview-empty">No preview asset</div>`;
9
+ return renderPageShell({
10
+ title: `${input.template.title} editor`,
11
+ mainClass: "is-wide",
12
+ runtimeBoot: input.editorChat ? {
13
+ pageKind: "legacy",
14
+ title: `${input.template.title} editor`,
15
+ userId: input.account.userId ?? null,
16
+ editorChat: input.editorChat
17
+ } : undefined,
18
+ style: `
19
+ ${TEMPLATE_EDITOR_SHELL_STYLES}
20
+
21
+ .editor-workbench,
22
+ .editor-main,
23
+ .editor-shell {
24
+ padding-bottom: 0;
25
+ }
26
+
27
+ .editor-shell {
28
+ display: grid;
29
+ gap: 18px;
30
+ min-height: 0;
31
+ grid-template-rows: auto auto minmax(0, 1fr);
32
+ }
33
+
34
+ .editor-hero,
35
+ .editor-section {
36
+ border: 1px solid rgba(102, 82, 43, 0.14);
37
+ border-radius: 28px;
38
+ background: rgba(255, 251, 244, 0.78);
39
+ box-shadow: 0 14px 34px rgba(15, 23, 42, 0.04);
40
+ }
41
+
42
+ .editor-panel {
43
+ border: 1px solid rgba(102, 82, 43, 0.14);
44
+ border-radius: 28px;
45
+ background: rgba(255, 251, 244, 0.78);
46
+ box-shadow: 0 14px 34px rgba(15, 23, 42, 0.04);
47
+ overflow: hidden;
48
+ }
49
+
50
+ .editor-panel > summary {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: space-between;
54
+ gap: 16px;
55
+ padding: 18px 22px;
56
+ cursor: pointer;
57
+ list-style: none;
58
+ }
59
+
60
+ .editor-panel > summary::-webkit-details-marker {
61
+ display: none;
62
+ }
63
+
64
+ .editor-panel-summary {
65
+ display: grid;
66
+ gap: 4px;
67
+ }
68
+
69
+ .editor-panel-summary h2 {
70
+ margin: 0;
71
+ font-size: 1.55rem;
72
+ letter-spacing: -0.04em;
73
+ }
74
+
75
+ .editor-panel-summary p {
76
+ margin: 0;
77
+ color: #617087;
78
+ line-height: 1.6;
79
+ }
80
+
81
+ .editor-panel-toggle {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ gap: 10px;
85
+ color: #445066;
86
+ font-size: 0.82rem;
87
+ font-weight: 700;
88
+ letter-spacing: 0.08em;
89
+ text-transform: uppercase;
90
+ white-space: nowrap;
91
+ }
92
+
93
+ .editor-panel-toggle::before {
94
+ content: "";
95
+ width: 11px;
96
+ height: 11px;
97
+ border-right: 2px solid currentColor;
98
+ border-bottom: 2px solid currentColor;
99
+ transform: rotate(45deg) translateY(-2px);
100
+ transition: transform 180ms ease;
101
+ }
102
+
103
+ .editor-panel[open] .editor-panel-toggle::before {
104
+ transform: rotate(225deg) translateY(-1px);
105
+ }
106
+
107
+ .editor-panel-body {
108
+ padding: 0 22px 22px;
109
+ }
110
+
111
+ .editor-panel-body > .editor-hero,
112
+ .editor-panel-body > .editor-section {
113
+ border: 0;
114
+ border-radius: 0;
115
+ background: transparent;
116
+ box-shadow: none;
117
+ padding: 0;
118
+ }
119
+
120
+ .editor-hero {
121
+ display: grid;
122
+ grid-template-columns: minmax(280px, 420px) minmax(0, 1fr);
123
+ gap: 22px;
124
+ padding: 22px;
125
+ }
126
+
127
+ .editor-preview {
128
+ width: 100%;
129
+ min-height: 320px;
130
+ max-height: 560px;
131
+ object-fit: contain;
132
+ border-radius: 24px;
133
+ border: 1px solid rgba(209, 219, 233, 0.96);
134
+ background: rgba(255, 255, 255, 0.92);
135
+ }
136
+
137
+ .editor-preview-empty {
138
+ display: grid;
139
+ place-items: center;
140
+ color: #74829c;
141
+ }
142
+
143
+ .editor-copy {
144
+ display: grid;
145
+ align-content: start;
146
+ gap: 18px;
147
+ min-width: 0;
148
+ }
149
+
150
+ .editor-kicker {
151
+ color: #8d99ae;
152
+ font-size: 0.85rem;
153
+ text-transform: uppercase;
154
+ letter-spacing: 0.12em;
155
+ }
156
+
157
+ .editor-title {
158
+ margin: 0;
159
+ font-size: clamp(2rem, 3.6vw, 3.1rem);
160
+ line-height: 0.94;
161
+ letter-spacing: -0.06em;
162
+ }
163
+
164
+ .editor-copy p {
165
+ margin: 0;
166
+ color: #5f6c83;
167
+ font-size: 1rem;
168
+ line-height: 1.7;
169
+ }
170
+
171
+ .editor-pills,
172
+ .editor-route-list {
173
+ display: flex;
174
+ flex-wrap: wrap;
175
+ gap: 10px;
176
+ }
177
+
178
+ .editor-toolbar {
179
+ display: flex;
180
+ flex-wrap: wrap;
181
+ gap: 10px;
182
+ margin-top: 14px;
183
+ }
184
+
185
+ .editor-copy-button {
186
+ appearance: none;
187
+ border: 1px solid rgba(102, 82, 43, 0.18);
188
+ background: rgba(255, 255, 255, 0.88);
189
+ color: #2f3c4f;
190
+ border-radius: 999px;
191
+ padding: 10px 14px;
192
+ font: inherit;
193
+ font-size: 0.88rem;
194
+ font-weight: 700;
195
+ cursor: pointer;
196
+ }
197
+
198
+ .editor-copy-button:hover {
199
+ border-color: rgba(102, 82, 43, 0.3);
200
+ background: #fff;
201
+ }
202
+
203
+ .editor-pill,
204
+ .editor-route-method {
205
+ display: inline-flex;
206
+ align-items: center;
207
+ justify-content: center;
208
+ min-height: 42px;
209
+ padding: 0 16px;
210
+ border-radius: 999px;
211
+ border: 1px solid rgba(191, 164, 109, 0.34);
212
+ background: rgba(255, 255, 255, 0.9);
213
+ }
214
+
215
+ .editor-grid {
216
+ display: grid;
217
+ grid-template-columns: minmax(0, 1fr);
218
+ gap: 18px;
219
+ min-height: 0;
220
+ }
221
+
222
+ .editor-section {
223
+ display: grid;
224
+ align-content: start;
225
+ gap: 16px;
226
+ padding: 22px;
227
+ }
228
+
229
+ .editor-section h2,
230
+ .editor-section h3 {
231
+ margin: 0;
232
+ }
233
+
234
+ .editor-section h2 {
235
+ font-size: 1.55rem;
236
+ letter-spacing: -0.04em;
237
+ }
238
+
239
+ .editor-section h3 {
240
+ font-size: 1rem;
241
+ color: #243041;
242
+ }
243
+
244
+ .editor-section-header {
245
+ display: grid;
246
+ gap: 6px;
247
+ }
248
+
249
+ .editor-section-header p,
250
+ .editor-note {
251
+ margin: 0;
252
+ color: #617087;
253
+ line-height: 1.7;
254
+ }
255
+
256
+ .editor-route-card {
257
+ display: grid;
258
+ gap: 10px;
259
+ padding: 16px;
260
+ border-radius: 20px;
261
+ border: 1px solid rgba(191, 164, 109, 0.22);
262
+ background: rgba(255, 255, 255, 0.72);
263
+ }
264
+
265
+ .editor-route-head {
266
+ display: flex;
267
+ flex-wrap: wrap;
268
+ align-items: center;
269
+ gap: 10px;
270
+ }
271
+
272
+ .editor-route-method {
273
+ min-height: 30px;
274
+ padding: 0 10px;
275
+ font-size: 0.77rem;
276
+ font-weight: 800;
277
+ }
278
+
279
+ .editor-route-method[data-method="POST"] {
280
+ color: #7a4b16;
281
+ }
282
+
283
+ .editor-route-method[data-method="GET"] {
284
+ color: #1e5c52;
285
+ }
286
+
287
+ .editor-route-path {
288
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
289
+ font-size: 0.82rem;
290
+ line-height: 1.5;
291
+ word-break: break-all;
292
+ color: #324156;
293
+ }
294
+
295
+ .editor-code-block {
296
+ margin: 0;
297
+ padding: 14px 16px;
298
+ border-radius: 18px;
299
+ background: rgba(37, 45, 61, 0.97);
300
+ color: #f7efe1;
301
+ overflow: auto;
302
+ font-size: 0.82rem;
303
+ line-height: 1.55;
304
+ }
305
+
306
+ .editor-quickstart-grid {
307
+ display: grid;
308
+ grid-template-columns: repeat(2, minmax(0, 1fr));
309
+ gap: 14px;
310
+ }
311
+
312
+ .editor-quickstart-field {
313
+ display: grid;
314
+ gap: 8px;
315
+ min-width: 0;
316
+ }
317
+
318
+ .editor-quickstart-field-head {
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: flex-start;
322
+ gap: 8px;
323
+ min-width: 0;
324
+ }
325
+
326
+ .editor-quickstart-field-head span {
327
+ color: #324156;
328
+ font-size: 0.82rem;
329
+ font-weight: 800;
330
+ }
331
+
332
+ .editor-quickstart-copy {
333
+ appearance: none;
334
+ box-sizing: border-box;
335
+ display: inline-flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ width: 36px;
339
+ min-width: 36px;
340
+ height: 36px;
341
+ min-height: 36px;
342
+ flex: 0 0 36px;
343
+ gap: 0;
344
+ padding: 0;
345
+ border-radius: 999px;
346
+ border: 1px solid rgba(171, 138, 68, 0.48);
347
+ background: #dbc17a;
348
+ color: #243041;
349
+ cursor: pointer;
350
+ line-height: 1;
351
+ box-shadow:
352
+ inset 0 1px 0 rgba(255, 255, 255, 0.58),
353
+ 0 6px 14px rgba(102, 82, 43, 0.12);
354
+ transition:
355
+ transform 120ms ease,
356
+ border-color 120ms ease,
357
+ background 120ms ease,
358
+ color 120ms ease;
359
+ }
360
+
361
+ .editor-quickstart-copy:hover {
362
+ border-color: rgba(102, 82, 43, 0.44);
363
+ background: #e4cb84;
364
+ color: #243041;
365
+ }
366
+
367
+ .editor-quickstart-copy:active {
368
+ transform: scale(0.94);
369
+ }
370
+
371
+ .editor-quickstart-copy::after {
372
+ content: "✓";
373
+ display: none;
374
+ color: currentColor;
375
+ font-size: 1rem;
376
+ font-weight: 900;
377
+ line-height: 1;
378
+ }
379
+
380
+ .editor-quickstart-copy[data-copying="true"] {
381
+ border-color: rgba(102, 82, 43, 0.5);
382
+ background: #ead89d;
383
+ color: #243041;
384
+ transform: scale(0.96);
385
+ }
386
+
387
+ .editor-quickstart-copy[data-copied="true"] {
388
+ border-color: rgba(30, 92, 82, 0.42);
389
+ background: #b9e1cf;
390
+ color: #1e5c52;
391
+ }
392
+
393
+ .editor-quickstart-copy[data-copy-error="true"] {
394
+ border-color: rgba(155, 45, 45, 0.42);
395
+ background: #f0c5bd;
396
+ color: #8d2f24;
397
+ }
398
+
399
+ .editor-quickstart-copy[data-copy-error="true"]::after {
400
+ content: "!";
401
+ display: block;
402
+ }
403
+
404
+ .editor-quickstart-copy[data-copied="true"]::after {
405
+ display: block;
406
+ }
407
+
408
+ .editor-quickstart-copy[data-copy-error="true"] svg,
409
+ .editor-quickstart-copy[data-copied="true"] svg {
410
+ display: none;
411
+ }
412
+
413
+ .editor-quickstart-copy svg {
414
+ display: block;
415
+ width: 17px;
416
+ height: 17px;
417
+ stroke: currentColor;
418
+ }
419
+
420
+ .editor-quickstart-textarea {
421
+ width: 100%;
422
+ min-height: 190px;
423
+ resize: vertical;
424
+ border: 1px solid rgba(191, 164, 109, 0.24);
425
+ border-radius: 18px;
426
+ background: rgba(255, 255, 255, 0.72);
427
+ color: #324156;
428
+ padding: 14px 15px;
429
+ font: 0.82rem/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
430
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.76);
431
+ }
432
+
433
+ .editor-quickstart-textarea:focus {
434
+ outline: 2px solid rgba(219, 193, 122, 0.38);
435
+ outline-offset: 2px;
436
+ }
437
+
438
+ .editor-quickstart-samples {
439
+ display: grid;
440
+ gap: 8px;
441
+ }
442
+
443
+ .editor-quickstart-samples > h3 {
444
+ margin: 2px 0 0;
445
+ }
446
+
447
+ .editor-sample-prompt-row {
448
+ display: flex;
449
+ gap: 10px;
450
+ overflow-x: auto;
451
+ overflow-y: hidden;
452
+ padding: 2px 0 8px;
453
+ scrollbar-width: thin;
454
+ }
455
+
456
+ .editor-sample-prompt {
457
+ flex: 0 0 min(360px, 86vw);
458
+ }
459
+
460
+ .editor-sample-prompt .editor-quickstart-textarea {
461
+ min-height: 150px;
462
+ resize: none;
463
+ }
464
+
465
+ .editor-history-toolbar {
466
+ display: flex;
467
+ flex-wrap: wrap;
468
+ align-items: center;
469
+ justify-content: space-between;
470
+ gap: 12px;
471
+ margin-bottom: 18px;
472
+ }
473
+
474
+ .editor-history-toolbar p {
475
+ margin: 0;
476
+ }
477
+
478
+ .editor-history-chip-row {
479
+ display: flex;
480
+ flex-wrap: wrap;
481
+ gap: 8px;
482
+ }
483
+
484
+ .editor-history-toolbar-actions,
485
+ .editor-history-group-actions {
486
+ display: flex;
487
+ flex-wrap: wrap;
488
+ align-items: center;
489
+ gap: 8px;
490
+ }
491
+
492
+ .editor-history-toolbar-actions {
493
+ justify-content: flex-end;
494
+ }
495
+
496
+ .editor-history-button,
497
+ .editor-history-remove {
498
+ display: inline-flex;
499
+ align-items: center;
500
+ justify-content: center;
501
+ min-height: 32px;
502
+ padding: 0 12px;
503
+ border-radius: 999px;
504
+ border: 1px solid rgba(191, 164, 109, 0.34);
505
+ background: rgba(255, 255, 255, 0.92);
506
+ color: #324156;
507
+ font-size: 0.78rem;
508
+ font-weight: 700;
509
+ cursor: pointer;
510
+ }
511
+
512
+ .editor-history-remove {
513
+ color: #8d4a3b;
514
+ }
515
+
516
+ .editor-history-button.is-primary {
517
+ background: #dbc17a;
518
+ color: #243041;
519
+ }
520
+
521
+ .editor-history-refresh {
522
+ width: 30px;
523
+ min-height: 30px;
524
+ padding: 0;
525
+ border-radius: 999px;
526
+ }
527
+
528
+ .editor-history-refresh svg {
529
+ width: 15px;
530
+ height: 15px;
531
+ }
532
+
533
+ .editor-history-refresh[data-loading="true"] svg {
534
+ animation: editor-history-refresh-spin 0.8s linear infinite;
535
+ }
536
+
537
+ .editor-history-menu-wrap {
538
+ position: relative;
539
+ display: inline-flex;
540
+ }
541
+
542
+ .editor-history-menu-button {
543
+ width: 30px;
544
+ min-height: 30px;
545
+ padding: 0;
546
+ }
547
+
548
+ .editor-history-menu {
549
+ position: absolute;
550
+ right: 0;
551
+ top: calc(100% + 6px);
552
+ z-index: 20;
553
+ display: none;
554
+ min-width: 148px;
555
+ padding: 6px;
556
+ border: 1px solid rgba(191, 164, 109, 0.28);
557
+ border-radius: 10px;
558
+ background: rgba(255, 252, 247, 0.98);
559
+ box-shadow: 0 14px 34px rgba(37, 45, 61, 0.16);
560
+ }
561
+
562
+ .editor-history-menu-wrap[data-open="true"] .editor-history-menu {
563
+ display: grid;
564
+ gap: 2px;
565
+ }
566
+
567
+ .editor-history-menu button {
568
+ all: unset;
569
+ padding: 8px 9px;
570
+ border-radius: 7px;
571
+ color: #324156;
572
+ font-size: 0.78rem;
573
+ font-weight: 700;
574
+ cursor: pointer;
575
+ }
576
+
577
+ .editor-history-menu button:hover {
578
+ background: rgba(219, 193, 122, 0.16);
579
+ }
580
+
581
+ .editor-history-menu button[data-danger="true"] {
582
+ color: #8d4a3b;
583
+ }
584
+
585
+ .editor-history-group-previews {
586
+ display: flex;
587
+ flex-wrap: nowrap;
588
+ gap: 6px;
589
+ margin-top: 10px;
590
+ overflow: hidden;
591
+ }
592
+
593
+ .editor-history-group-previews.is-expanded {
594
+ max-width: min(100%, calc(100vw - 160px));
595
+ overflow-x: auto;
596
+ overflow-y: hidden;
597
+ padding-bottom: 5px;
598
+ scrollbar-width: thin;
599
+ }
600
+
601
+ .editor-history-group-preview {
602
+ position: relative;
603
+ width: 52px;
604
+ height: 52px;
605
+ padding: 0;
606
+ border-radius: 9px;
607
+ border: 1px solid rgba(191, 164, 109, 0.26);
608
+ background: rgba(255, 255, 255, 0.74);
609
+ overflow: hidden;
610
+ flex: 0 0 auto;
611
+ cursor: zoom-in;
612
+ }
613
+
614
+ .editor-history-group-preview img,
615
+ .editor-history-group-preview video {
616
+ width: 100%;
617
+ height: 100%;
618
+ object-fit: cover;
619
+ display: block;
620
+ }
621
+
622
+ .editor-history-group-preview-more {
623
+ display: inline-flex;
624
+ align-items: center;
625
+ justify-content: center;
626
+ padding: 0;
627
+ width: 52px;
628
+ height: 52px;
629
+ border-radius: 9px;
630
+ border: 1px dashed rgba(191, 164, 109, 0.32);
631
+ color: #617087;
632
+ font-size: 0.75rem;
633
+ font-weight: 800;
634
+ flex: 0 0 auto;
635
+ cursor: pointer;
636
+ }
637
+
638
+ button.editor-history-group-preview-more:hover,
639
+ button.editor-history-group-preview-more:focus-visible {
640
+ border-style: solid;
641
+ color: #324156;
642
+ background: rgba(255, 255, 255, 0.9);
643
+ }
644
+
645
+ .editor-history-chip {
646
+ display: inline-flex;
647
+ align-items: center;
648
+ min-height: 30px;
649
+ padding: 0 10px;
650
+ border-radius: 999px;
651
+ border: 1px solid rgba(191, 164, 109, 0.34);
652
+ background: rgba(255, 255, 255, 0.9);
653
+ color: #3b465a;
654
+ font-size: 0.78rem;
655
+ font-weight: 700;
656
+ }
657
+
658
+ .editor-history-chip.is-empty {
659
+ color: #74829c;
660
+ border-style: dashed;
661
+ }
662
+
663
+ .editor-history-groups {
664
+ display: grid;
665
+ gap: 12px;
666
+ }
667
+
668
+ .editor-history-empty {
669
+ padding: 18px;
670
+ border-radius: 18px;
671
+ border: 1px dashed rgba(191, 164, 109, 0.34);
672
+ background: rgba(255, 255, 255, 0.56);
673
+ color: #617087;
674
+ }
675
+
676
+ .editor-history-group {
677
+ border: 1px solid rgba(191, 164, 109, 0.22);
678
+ border-radius: 20px;
679
+ background: rgba(255, 255, 255, 0.62);
680
+ overflow: hidden;
681
+ }
682
+
683
+ .editor-history-group > summary {
684
+ display: flex;
685
+ align-items: center;
686
+ justify-content: space-between;
687
+ gap: 12px;
688
+ padding: 14px 16px;
689
+ cursor: pointer;
690
+ list-style: none;
691
+ }
692
+
693
+ .editor-history-group > summary::-webkit-details-marker {
694
+ display: none;
695
+ }
696
+
697
+ .editor-history-group-main {
698
+ display: grid;
699
+ gap: 4px;
700
+ min-width: 0;
701
+ }
702
+
703
+ .editor-history-group-main h3 {
704
+ margin: 0;
705
+ font-size: 1rem;
706
+ color: #243041;
707
+ word-break: break-all;
708
+ }
709
+
710
+ .editor-history-group-main p {
711
+ margin: 0;
712
+ color: #74829c;
713
+ font-size: 0.82rem;
714
+ }
715
+
716
+ .editor-history-group-toggle {
717
+ color: #617087;
718
+ font-size: 0.8rem;
719
+ font-weight: 700;
720
+ letter-spacing: 0.08em;
721
+ text-transform: uppercase;
722
+ }
723
+
724
+ .editor-history-active {
725
+ display: inline-flex;
726
+ align-items: center;
727
+ min-height: 26px;
728
+ padding: 0 9px;
729
+ border-radius: 999px;
730
+ background: rgba(219, 193, 122, 0.2);
731
+ color: #7a4b16;
732
+ font-size: 0.74rem;
733
+ font-weight: 800;
734
+ letter-spacing: 0.08em;
735
+ text-transform: uppercase;
736
+ }
737
+
738
+ @keyframes editor-history-refresh-spin {
739
+ to {
740
+ transform: rotate(360deg);
741
+ }
742
+ }
743
+
744
+ .editor-history-group-body {
745
+ display: grid;
746
+ gap: 10px;
747
+ padding: 0 16px 16px;
748
+ }
749
+
750
+ .editor-history-card {
751
+ display: grid;
752
+ gap: 8px;
753
+ padding: 10px;
754
+ border-radius: 12px;
755
+ border: 1px solid rgba(191, 164, 109, 0.18);
756
+ background: rgba(255, 251, 244, 0.92);
757
+ }
758
+
759
+ .editor-history-card-head {
760
+ display: flex;
761
+ flex-wrap: wrap;
762
+ justify-content: space-between;
763
+ gap: 10px;
764
+ }
765
+
766
+ .editor-history-card-main {
767
+ display: grid;
768
+ gap: 6px;
769
+ min-width: 0;
770
+ }
771
+
772
+ .editor-history-card-meta {
773
+ display: flex;
774
+ flex-wrap: wrap;
775
+ gap: 8px;
776
+ }
777
+
778
+ .editor-history-method,
779
+ .editor-history-status {
780
+ display: inline-flex;
781
+ align-items: center;
782
+ min-height: 28px;
783
+ padding: 0 10px;
784
+ border-radius: 999px;
785
+ border: 1px solid rgba(191, 164, 109, 0.3);
786
+ background: rgba(255, 255, 255, 0.92);
787
+ font-size: 0.78rem;
788
+ font-weight: 800;
789
+ }
790
+
791
+ .editor-history-method {
792
+ color: #7a4b16;
793
+ }
794
+
795
+ .editor-history-status.is-success {
796
+ color: #1e5c52;
797
+ }
798
+
799
+ .editor-history-status.is-error {
800
+ color: #9b2d2d;
801
+ }
802
+
803
+ .editor-history-card-path {
804
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
805
+ font-size: 0.8rem;
806
+ line-height: 1.55;
807
+ color: #324156;
808
+ word-break: break-all;
809
+ }
810
+
811
+ .editor-history-card-time {
812
+ color: #74829c;
813
+ font-size: 0.8rem;
814
+ white-space: nowrap;
815
+ }
816
+
817
+ .editor-history-card-previews {
818
+ display: flex;
819
+ flex-wrap: wrap;
820
+ gap: 8px;
821
+ }
822
+
823
+ .editor-history-card-preview {
824
+ position: relative;
825
+ width: 112px;
826
+ height: 112px;
827
+ display: inline-flex;
828
+ align-items: center;
829
+ justify-content: center;
830
+ overflow: hidden;
831
+ padding: 0;
832
+ border-radius: 18px;
833
+ border: 1px solid rgba(191, 164, 109, 0.34);
834
+ background: rgba(255, 255, 255, 0.94);
835
+ color: #324156;
836
+ text-decoration: none;
837
+ cursor: pointer;
838
+ }
839
+
840
+ .editor-history-video-play {
841
+ position: absolute;
842
+ left: 50%;
843
+ top: 50%;
844
+ width: 34px;
845
+ height: 34px;
846
+ transform: translate(-50%, -50%);
847
+ display: inline-flex;
848
+ align-items: center;
849
+ justify-content: center;
850
+ border-radius: 999px;
851
+ background: rgba(18, 24, 34, 0.72);
852
+ box-shadow: 0 8px 24px rgba(12, 18, 28, 0.22);
853
+ pointer-events: none;
854
+ }
855
+
856
+ .editor-history-group-preview .editor-history-video-play {
857
+ width: 24px;
858
+ height: 24px;
859
+ }
860
+
861
+ .editor-history-video-play::before {
862
+ content: "";
863
+ display: block;
864
+ width: 0;
865
+ height: 0;
866
+ margin-left: 3px;
867
+ border-top: 7px solid transparent;
868
+ border-bottom: 7px solid transparent;
869
+ border-left: 11px solid #fff8ee;
870
+ }
871
+
872
+ .editor-history-group-preview .editor-history-video-play::before {
873
+ margin-left: 2px;
874
+ border-top-width: 5px;
875
+ border-bottom-width: 5px;
876
+ border-left-width: 8px;
877
+ }
878
+
879
+ .editor-history-card-preview img,
880
+ .editor-history-card-preview video {
881
+ width: 100%;
882
+ height: 100%;
883
+ object-fit: cover;
884
+ display: block;
885
+ }
886
+
887
+ .editor-history-card-preview-label {
888
+ position: absolute;
889
+ left: 8px;
890
+ right: 8px;
891
+ bottom: 8px;
892
+ min-height: 22px;
893
+ display: inline-flex;
894
+ align-items: center;
895
+ justify-content: center;
896
+ padding: 0 8px;
897
+ border-radius: 999px;
898
+ background: rgba(24, 30, 42, 0.72);
899
+ color: #fff8ee;
900
+ font-size: 0.72rem;
901
+ font-weight: 700;
902
+ letter-spacing: 0.02em;
903
+ }
904
+
905
+ .editor-history-card-preview-fallback {
906
+ padding: 0 10px;
907
+ text-align: center;
908
+ font-size: 0.78rem;
909
+ font-weight: 700;
910
+ line-height: 1.4;
911
+ }
912
+
913
+ .editor-history-card-grid {
914
+ display: grid;
915
+ grid-template-columns: repeat(2, minmax(0, 1fr));
916
+ gap: 12px;
917
+ }
918
+
919
+ .editor-history-card .vf-editor-chat-http-card {
920
+ background: rgba(248, 244, 235, 0.74);
921
+ }
922
+
923
+ .editor-history-card-foot {
924
+ display: flex;
925
+ justify-content: flex-end;
926
+ color: #7b879b;
927
+ font-size: 0.72rem;
928
+ line-height: 1.3;
929
+ }
930
+
931
+ .editor-history-sample {
932
+ display: grid;
933
+ gap: 8px;
934
+ }
935
+
936
+ .editor-history-sample h4 {
937
+ margin: 0;
938
+ font-size: 0.82rem;
939
+ color: #445066;
940
+ text-transform: uppercase;
941
+ letter-spacing: 0.08em;
942
+ }
943
+
944
+ .editor-history-sample pre {
945
+ margin: 0;
946
+ min-height: 90px;
947
+ max-height: 280px;
948
+ overflow: auto;
949
+ padding: 12px;
950
+ border-radius: 14px;
951
+ background: rgba(37, 45, 61, 0.96);
952
+ color: #f7efe1;
953
+ font-size: 0.78rem;
954
+ line-height: 1.5;
955
+ }
956
+
957
+ .editor-history-sample-note {
958
+ color: #74829c;
959
+ font-size: 0.78rem;
960
+ }
961
+
962
+ .editor-modal-backdrop {
963
+ position: fixed;
964
+ inset: 0;
965
+ display: none;
966
+ align-items: center;
967
+ justify-content: center;
968
+ padding: 24px;
969
+ background: rgba(24, 30, 42, 0.52);
970
+ backdrop-filter: blur(10px);
971
+ z-index: 1200;
972
+ }
973
+
974
+ .editor-modal-backdrop[data-open="true"] {
975
+ display: flex;
976
+ }
977
+
978
+ .editor-modal {
979
+ width: min(100%, 460px);
980
+ display: grid;
981
+ gap: 16px;
982
+ padding: 22px;
983
+ border-radius: 26px;
984
+ border: 1px solid rgba(191, 164, 109, 0.28);
985
+ background: rgba(255, 251, 244, 0.98);
986
+ box-shadow: 0 24px 64px rgba(15, 23, 42, 0.22);
987
+ }
988
+
989
+ .editor-modal-head {
990
+ display: grid;
991
+ gap: 6px;
992
+ }
993
+
994
+ .editor-modal-head h3 {
995
+ margin: 0;
996
+ font-size: 1.2rem;
997
+ }
998
+
999
+ .editor-modal-head p,
1000
+ .editor-modal-error,
1001
+ .editor-modal-label {
1002
+ margin: 0;
1003
+ color: #617087;
1004
+ line-height: 1.6;
1005
+ }
1006
+
1007
+ .editor-modal-label {
1008
+ font-size: 0.82rem;
1009
+ font-weight: 700;
1010
+ letter-spacing: 0.08em;
1011
+ text-transform: uppercase;
1012
+ }
1013
+
1014
+ .editor-modal-input {
1015
+ width: 100%;
1016
+ min-height: 50px;
1017
+ padding: 0 14px;
1018
+ border-radius: 16px;
1019
+ border: 1px solid rgba(191, 164, 109, 0.34);
1020
+ background: rgba(255, 255, 255, 0.96);
1021
+ color: #243041;
1022
+ font: inherit;
1023
+ }
1024
+
1025
+ .editor-modal-input:focus {
1026
+ outline: 2px solid rgba(219, 193, 122, 0.7);
1027
+ outline-offset: 2px;
1028
+ }
1029
+
1030
+ .editor-modal-error {
1031
+ min-height: 1.4em;
1032
+ color: #9b2d2d;
1033
+ font-size: 0.84rem;
1034
+ }
1035
+
1036
+ .editor-modal-actions {
1037
+ display: flex;
1038
+ justify-content: flex-end;
1039
+ gap: 10px;
1040
+ }
1041
+
1042
+ .editor-media-modal {
1043
+ width: min(100%, 980px);
1044
+ max-height: calc(100vh - 48px);
1045
+ display: grid;
1046
+ gap: 14px;
1047
+ padding: 18px;
1048
+ border-radius: 28px;
1049
+ border: 1px solid rgba(191, 164, 109, 0.28);
1050
+ background: rgba(255, 251, 244, 0.985);
1051
+ box-shadow: 0 28px 80px rgba(15, 23, 42, 0.28);
1052
+ }
1053
+
1054
+ .editor-media-modal-head {
1055
+ display: flex;
1056
+ align-items: center;
1057
+ justify-content: space-between;
1058
+ gap: 12px;
1059
+ }
1060
+
1061
+ .editor-media-modal-actions {
1062
+ display: inline-flex;
1063
+ align-items: center;
1064
+ gap: 10px;
1065
+ flex: 0 0 auto;
1066
+ }
1067
+
1068
+ .editor-media-modal-source {
1069
+ color: #617087;
1070
+ font-size: 0.82rem;
1071
+ font-weight: 700;
1072
+ text-decoration: underline;
1073
+ text-underline-offset: 0.14em;
1074
+ }
1075
+
1076
+ .editor-media-modal-title {
1077
+ margin: 0;
1078
+ color: #324156;
1079
+ font-size: 0.95rem;
1080
+ font-weight: 700;
1081
+ word-break: break-all;
1082
+ }
1083
+
1084
+ .editor-media-modal-body {
1085
+ min-height: 0;
1086
+ display: grid;
1087
+ place-items: center;
1088
+ overflow: auto;
1089
+ border-radius: 20px;
1090
+ background: rgba(37, 45, 61, 0.96);
1091
+ }
1092
+
1093
+ .editor-media-modal-body img,
1094
+ .editor-media-modal-body video {
1095
+ max-width: 100%;
1096
+ max-height: calc(100vh - 180px);
1097
+ display: block;
1098
+ object-fit: contain;
1099
+ }
1100
+
1101
+ @media (max-width: 767px) {
1102
+ .editor-hero,
1103
+ .editor-grid {
1104
+ grid-template-columns: 1fr;
1105
+ }
1106
+
1107
+ .editor-history-card-grid {
1108
+ grid-template-columns: 1fr;
1109
+ }
1110
+
1111
+ .editor-quickstart-grid {
1112
+ grid-template-columns: 1fr;
1113
+ }
1114
+
1115
+ .editor-hero,
1116
+ .editor-section,
1117
+ .editor-panel {
1118
+ border-radius: 22px;
1119
+ }
1120
+
1121
+ .editor-panel > summary {
1122
+ align-items: flex-start;
1123
+ padding: 16px;
1124
+ }
1125
+
1126
+ .editor-panel-toggle {
1127
+ font-size: 0;
1128
+ }
1129
+
1130
+ .editor-panel-toggle::before {
1131
+ width: 10px;
1132
+ height: 10px;
1133
+ }
1134
+
1135
+ .editor-panel-body {
1136
+ padding: 0 16px 16px;
1137
+ }
1138
+
1139
+ .editor-hero,
1140
+ .editor-section {
1141
+ padding: 16px;
1142
+ }
1143
+
1144
+ .editor-preview {
1145
+ min-height: 0;
1146
+ max-height: none;
1147
+ aspect-ratio: 16 / 11;
1148
+ border-radius: 18px;
1149
+ }
1150
+
1151
+ .editor-title {
1152
+ font-size: clamp(2rem, 12vw, 2.85rem);
1153
+ line-height: 0.95;
1154
+ }
1155
+
1156
+ .editor-toolbar,
1157
+ .editor-history-toolbar-actions,
1158
+ .editor-history-group-actions {
1159
+ display: grid;
1160
+ grid-template-columns: 1fr;
1161
+ width: 100%;
1162
+ }
1163
+
1164
+ .editor-copy-button,
1165
+ .editor-history-button,
1166
+ .editor-history-remove {
1167
+ width: 100%;
1168
+ }
1169
+
1170
+ .editor-pill,
1171
+ .editor-route-path,
1172
+ .editor-history-chip {
1173
+ max-width: 100%;
1174
+ overflow: hidden;
1175
+ text-overflow: ellipsis;
1176
+ white-space: nowrap;
1177
+ }
1178
+ }
1179
+ `,
1180
+ body: `
1181
+ <section class="editor-workbench">
1182
+ ${renderTemplateEditorChrome({
1183
+ backHref,
1184
+ templateTitle: input.template.title,
1185
+ templateId: input.template.id,
1186
+ templateSlug: input.template.slugId
1187
+ })}
1188
+ <main class="editor-main">
1189
+ <section class="editor-shell page-content-start">
1190
+ <details class="editor-panel" open>
1191
+ <summary>
1192
+ <div class="editor-panel-summary">
1193
+ <h2>Character card</h2>
1194
+ <p>Preview the format, metadata, and editorial notes without losing space for the sections that follow.</p>
1195
+ </div>
1196
+ <span class="editor-panel-toggle" aria-hidden="true">Toggle</span>
1197
+ </summary>
1198
+ <div class="editor-panel-body">
1199
+ <section class="editor-hero">
1200
+ ${preview}
1201
+ <div class="editor-copy">
1202
+ <div class="editor-kicker">Template Editor Docs</div>
1203
+ <h1 class="editor-title">${escapeHtml(input.template.title)}</h1>
1204
+ <div class="editor-pills">
1205
+ <span class="editor-pill">${escapeHtml(input.template.id)}</span>
1206
+ <span class="editor-pill">${escapeHtml(input.template.slugId)}</span>
1207
+ <span class="editor-pill">v${escapeHtml(input.template.version)}</span>
1208
+ </div>
1209
+ <p>${escapeHtml(input.template.viralDna || input.template.visualDna || "No editorial summary is available for this template yet.")}</p>
1210
+ ${input.directorSkill?.trim()
1211
+ ? `<div class="editor-toolbar">
1212
+ <button
1213
+ type="button"
1214
+ class="editor-copy-button"
1215
+ data-editor-copy-content="${escapeAttribute(input.directorSkill)}"
1216
+ data-editor-copy-label="Director SKILL.md"
1217
+ >Copy Director SKILL.md</button>
1218
+ </div>`
1219
+ : ""}
1220
+ </div>
1221
+ </section>
1222
+ </div>
1223
+ </details>
1224
+ <section class="editor-grid">
1225
+ <details class="editor-panel" open>
1226
+ <summary>
1227
+ <div class="editor-panel-summary">
1228
+ <h2>Quickstart</h2>
1229
+ <p>Use the template prompt scaffolds as readonly references before calling operations or continuing in Copilot.</p>
1230
+ </div>
1231
+ <span class="editor-panel-toggle" aria-hidden="true">Toggle</span>
1232
+ </summary>
1233
+ <div class="editor-panel-body">
1234
+ <section class="editor-section">
1235
+ <div class="editor-quickstart-grid">
1236
+ ${renderQuickstartTextarea("Skeleton prompt", input.template.skeletonPrompt, 10)}
1237
+ ${renderQuickstartTextarea("Proposal Generator", input.template.proposalGenerator, 10)}
1238
+ </div>
1239
+ <div class="editor-quickstart-samples">
1240
+ <h3>Sample prompts</h3>
1241
+ <div class="editor-sample-prompt-row" aria-label="Sample prompts">
1242
+ ${input.template.samplePrompts.length
1243
+ ? input.template.samplePrompts.map((sample, index) => `
1244
+ <div class="editor-sample-prompt">
1245
+ ${renderQuickstartTextarea(`Sample ${index + 1}`, sample, 7)}
1246
+ </div>
1247
+ `).join("")
1248
+ : `<div class="editor-sample-prompt">${renderQuickstartTextarea("Sample 1", "No sample prompts are defined for this template yet.", 7)}</div>`}
1249
+ </div>
1250
+ </div>
1251
+ </section>
1252
+ </div>
1253
+ </details>
1254
+ <details class="editor-panel">
1255
+ <summary>
1256
+ <div class="editor-panel-summary">
1257
+ <h2>REST API docs</h2>
1258
+ <p>This editor combines template routes with platform primitive routes so asset generation, image edits, and template runs stay in one tracer-aware workflow.</p>
1259
+ </div>
1260
+ <span class="editor-panel-toggle" aria-hidden="true">Toggle</span>
1261
+ </summary>
1262
+ <div class="editor-panel-body">
1263
+ <section class="editor-section">
1264
+ <div class="editor-section-header">
1265
+ <p>Use tracer IDs to group a run across requests.</p>
1266
+ </div>
1267
+ <div class="editor-route-list">
1268
+ ${input.docsRoutes.map((route) => `
1269
+ <article class="editor-route-card">
1270
+ <div class="editor-route-head">
1271
+ <span class="editor-route-method" data-method="${escapeAttribute(route.method)}">${escapeHtml(route.method)}</span>
1272
+ <div class="editor-route-path">${escapeHtml(route.path)}</div>
1273
+ </div>
1274
+ <div class="editor-note">${escapeHtml(route.summary)}</div>
1275
+ </article>
1276
+ `).join("")}
1277
+ </div>
1278
+ </section>
1279
+ </div>
1280
+ </details>
1281
+ <details class="editor-panel" open>
1282
+ <summary>
1283
+ <div class="editor-panel-summary">
1284
+ <h2>Tracer Logs</h2>
1285
+ <p>Recent POST calls are grouped by tracer so parallel workstreams stay readable. Newest requests appear first inside each tracer group.</p>
1286
+ </div>
1287
+ <span class="editor-panel-toggle" aria-hidden="true">Toggle</span>
1288
+ </summary>
1289
+ <div class="editor-panel-body">
1290
+ <section class="editor-section">
1291
+ <div class="editor-history-toolbar">
1292
+ <p class="editor-note">Tracer visibility lives here. Removing one only clears it from this page view; the underlying runs remain available in Job Runs.</p>
1293
+ <div class="editor-history-toolbar-actions">
1294
+ <button type="button" class="editor-history-button is-primary" data-editor-history-add>Add tracer</button>
1295
+ <div class="editor-history-chip-row" data-editor-history-tracers>
1296
+ ${input.initialTracers.length
1297
+ ? input.initialTracers.map((tracer) => `<span class="editor-history-chip">${escapeHtml(tracer)}</span>`).join("")
1298
+ : `<span class="editor-history-chip is-empty">No tracer selected yet</span>`}
1299
+ </div>
1300
+ </div>
1301
+ </div>
1302
+ <div class="editor-history-groups" data-editor-history-root>
1303
+ <div class="editor-history-empty">${hasHistoryAccess ? "Loading POST history…" : "Sign in to load tracer-scoped POST history."}</div>
1304
+ </div>
1305
+ </section>
1306
+ </div>
1307
+ </details>
1308
+ </section>
1309
+ </section>
1310
+ </main>
1311
+ </section>
1312
+ <div class="editor-modal-backdrop" data-editor-tracer-modal data-open="false" hidden>
1313
+ <div class="editor-modal" role="dialog" aria-modal="true" aria-labelledby="editor-tracer-modal-title">
1314
+ <div class="editor-modal-head">
1315
+ <h3 id="editor-tracer-modal-title">Add tracer</h3>
1316
+ <p>Type a tracer label to pin it into this page view. Existing labels stay untouched.</p>
1317
+ </div>
1318
+ <label class="editor-modal-label" for="editor-tracer-input">Tracer id</label>
1319
+ <input id="editor-tracer-input" class="editor-modal-input" type="text" autocomplete="off" spellcheck="false" placeholder="${escapeAttribute(`${input.template.slugId}-manual`)}" />
1320
+ <p class="editor-modal-error" data-editor-tracer-modal-error aria-live="polite"></p>
1321
+ <div class="editor-modal-actions">
1322
+ <button type="button" class="editor-history-button" data-editor-tracer-cancel>Cancel</button>
1323
+ <button type="button" class="editor-history-button is-primary" data-editor-tracer-submit>Add tracer</button>
1324
+ </div>
1325
+ </div>
1326
+ </div>
1327
+ <div class="editor-modal-backdrop" data-editor-media-modal data-open="false" hidden>
1328
+ <div class="editor-media-modal" role="dialog" aria-modal="true" aria-labelledby="editor-media-modal-title">
1329
+ <div class="editor-media-modal-head">
1330
+ <p id="editor-media-modal-title" class="editor-media-modal-title">Output preview</p>
1331
+ <div class="editor-media-modal-actions">
1332
+ <a class="editor-media-modal-source" data-editor-media-source href="#" target="_blank" rel="noreferrer">Open source</a>
1333
+ <button type="button" class="editor-history-button" data-editor-media-close>Close</button>
1334
+ </div>
1335
+ </div>
1336
+ <div class="editor-media-modal-body" data-editor-media-body></div>
1337
+ </div>
1338
+ </div>
1339
+ <div class="vf-editor-chat-http-modal-backdrop" data-editor-http-modal hidden>
1340
+ <div class="vf-editor-chat-http-modal" role="dialog" aria-modal="true" aria-labelledby="editor-http-modal-title">
1341
+ <div class="vf-editor-chat-http-modal-header">
1342
+ <div class="vf-editor-chat-http-modal-header-main" data-editor-http-modal-head>
1343
+ <span id="editor-http-modal-title" class="vf-editor-chat-http-modal-url">HTTP details</span>
1344
+ </div>
1345
+ <button type="button" class="vf-editor-chat-http-modal-close" data-editor-http-close aria-label="Close HTTP details">×</button>
1346
+ </div>
1347
+ <div class="vf-editor-chat-http-card-body is-modal" data-editor-http-modal-body></div>
1348
+ </div>
1349
+ </div>
1350
+ <script>
1351
+ ${TEMPLATE_EDITOR_SHELL_SCRIPT}
1352
+ (() => {
1353
+ const historyRoot = document.querySelector("[data-editor-history-root]");
1354
+ const tracerChipRow = document.querySelector("[data-editor-history-tracers]");
1355
+ const addTracerButton = document.querySelector("[data-editor-history-add]");
1356
+ const modalBackdrop = document.querySelector("[data-editor-tracer-modal]");
1357
+ const modalInput = document.getElementById("editor-tracer-input");
1358
+ const modalError = document.querySelector("[data-editor-tracer-modal-error]");
1359
+ const modalSubmit = document.querySelector("[data-editor-tracer-submit]");
1360
+ const modalCancel = document.querySelector("[data-editor-tracer-cancel]");
1361
+ const mediaModalBackdrop = document.querySelector("[data-editor-media-modal]");
1362
+ const mediaModalBody = document.querySelector("[data-editor-media-body]");
1363
+ const mediaModalTitle = document.getElementById("editor-media-modal-title");
1364
+ const mediaModalClose = document.querySelector("[data-editor-media-close]");
1365
+ const mediaModalSource = document.querySelector("[data-editor-media-source]");
1366
+ const httpModalBackdrop = document.querySelector("[data-editor-http-modal]");
1367
+ const httpModalHead = document.querySelector("[data-editor-http-modal-head]");
1368
+ const httpModalBody = document.querySelector("[data-editor-http-modal-body]");
1369
+ const httpModalClose = document.querySelector("[data-editor-http-close]");
1370
+ if (!(historyRoot instanceof HTMLElement) || !(tracerChipRow instanceof HTMLElement)) {
1371
+ return;
1372
+ }
1373
+
1374
+ const hasTracerModal = addTracerButton instanceof HTMLButtonElement
1375
+ && modalBackdrop instanceof HTMLElement
1376
+ && modalInput instanceof HTMLInputElement
1377
+ && modalError instanceof HTMLElement
1378
+ && modalSubmit instanceof HTMLButtonElement
1379
+ && modalCancel instanceof HTMLButtonElement;
1380
+ const hasMediaModal = mediaModalBackdrop instanceof HTMLElement
1381
+ && mediaModalBody instanceof HTMLElement
1382
+ && mediaModalTitle instanceof HTMLElement
1383
+ && mediaModalClose instanceof HTMLButtonElement
1384
+ && mediaModalSource instanceof HTMLAnchorElement;
1385
+ const hasHttpModal = httpModalBackdrop instanceof HTMLElement
1386
+ && httpModalHead instanceof HTMLElement
1387
+ && httpModalBody instanceof HTMLElement
1388
+ && httpModalClose instanceof HTMLButtonElement;
1389
+
1390
+ const boot = {
1391
+ historyEndpoint: ${JSON.stringify(input.historyEndpoint)},
1392
+ tracerEndpoint: ${JSON.stringify(input.tracerEndpoint)},
1393
+ fallbackHistoryEndpoint: ${JSON.stringify(withAccountQuery(`/job-runs/history?limit=250&template_id=${encodeURIComponent(input.template.id)}`, input.account.userId))},
1394
+ isLoggedIn: ${input.account.isLoggedIn ? "true" : "false"}
1395
+ };
1396
+ let lastHistoryEntries = [];
1397
+ let lastSelectedTracers = [];
1398
+ const expandedPreviewGroups = new Set();
1399
+
1400
+ function escapeHtml(value) {
1401
+ return String(value)
1402
+ .replace(/&/g, "&amp;")
1403
+ .replace(/</g, "&lt;")
1404
+ .replace(/>/g, "&gt;")
1405
+ .replace(/"/g, "&quot;");
1406
+ }
1407
+
1408
+ function formatJson(value) {
1409
+ if (value == null) {
1410
+ return "null";
1411
+ }
1412
+ if (typeof value === "string") {
1413
+ return value;
1414
+ }
1415
+ try {
1416
+ return JSON.stringify(value, null, 2);
1417
+ } catch {
1418
+ return String(value);
1419
+ }
1420
+ }
1421
+
1422
+ function setTextBlock(parent, label, value) {
1423
+ const block = document.createElement("div");
1424
+ block.className = "vf-editor-chat-http-block";
1425
+ const heading = document.createElement("div");
1426
+ heading.className = "vf-editor-chat-http-label";
1427
+ heading.textContent = label;
1428
+ const pre = document.createElement("pre");
1429
+ pre.className = "vf-editor-chat-http-pre";
1430
+ pre.textContent = typeof value === "string" ? value : formatJson(value);
1431
+ block.append(heading, pre);
1432
+ parent.append(block);
1433
+ }
1434
+
1435
+ function makeHttpPayload(entry) {
1436
+ return {
1437
+ method: entry.method,
1438
+ url: entry.path,
1439
+ status: Number(entry.status) || 0,
1440
+ statusText: "",
1441
+ request: {
1442
+ query: entry.request?.query || {},
1443
+ body: entry.request?.body ?? null,
1444
+ omitted: entry.request?.bodyOmittedReason || null
1445
+ },
1446
+ response: {
1447
+ status: entry.status,
1448
+ body: entry.response?.body ?? null,
1449
+ omitted: entry.response?.bodyOmittedReason || null
1450
+ }
1451
+ };
1452
+ }
1453
+
1454
+ function encodeHttpPayload(entry) {
1455
+ try {
1456
+ return encodeURIComponent(JSON.stringify(makeHttpPayload(entry)));
1457
+ } catch {
1458
+ return encodeURIComponent(JSON.stringify({
1459
+ method: entry.method,
1460
+ url: entry.path,
1461
+ status: Number(entry.status) || 0,
1462
+ statusText: "",
1463
+ request: { query: {}, body: null, omitted: "Request sample could not be encoded for display." },
1464
+ response: { status: entry.status, body: null, omitted: "Response sample could not be encoded for display." }
1465
+ }));
1466
+ }
1467
+ }
1468
+
1469
+ function renderHttpAction(entry) {
1470
+ const payload = encodeHttpPayload(entry);
1471
+ const status = Number(entry.status) || 0;
1472
+ const isError = status >= 400;
1473
+ const isSuccess = status >= 200 && status < 300;
1474
+ return '<div class="vf-editor-chat-http-card">' +
1475
+ '<button type="button" class="vf-editor-chat-http-card-header" data-editor-http-detail="' + escapeHtml(payload) + '" aria-label="Open HTTP details for ' + escapeHtml(entry.method) + ' ' + escapeHtml(entry.path) + '">' +
1476
+ '<span class="vf-editor-chat-http-pill">' + escapeHtml(entry.method) + '</span>' +
1477
+ '<span class="vf-editor-chat-http-url" title="' + escapeHtml(entry.path) + '">' + escapeHtml(entry.path) + '</span>' +
1478
+ '<span class="vf-editor-chat-http-status" data-error="' + (isError ? "true" : "false") + '" data-success="' + (isSuccess ? "true" : "false") + '">' + escapeHtml(status) + '</span>' +
1479
+ '<span class="vf-editor-chat-http-expand" aria-hidden="true">+</span>' +
1480
+ '</button>' +
1481
+ '</div>';
1482
+ }
1483
+
1484
+ function openHttpModal(payload) {
1485
+ if (!hasHttpModal) {
1486
+ return;
1487
+ }
1488
+ httpModalHead.innerHTML = "";
1489
+ const isError = payload.status >= 400;
1490
+ const isSuccess = payload.status >= 200 && payload.status < 300;
1491
+ const method = document.createElement("span");
1492
+ method.className = "vf-editor-chat-http-pill";
1493
+ method.textContent = payload.method || "HTTP";
1494
+ const url = document.createElement("span");
1495
+ url.className = "vf-editor-chat-http-modal-url";
1496
+ url.textContent = payload.url || "";
1497
+ const status = document.createElement("span");
1498
+ status.className = "vf-editor-chat-http-status";
1499
+ status.dataset.error = isError ? "true" : "false";
1500
+ status.dataset.success = isSuccess ? "true" : "false";
1501
+ status.textContent = String(payload.status || "");
1502
+ httpModalHead.append(method, url, status);
1503
+ httpModalBody.innerHTML = "";
1504
+ setTextBlock(httpModalBody, "URL", payload.url || "");
1505
+ if (payload.request?.omitted) {
1506
+ setTextBlock(httpModalBody, "Request note", payload.request.omitted);
1507
+ }
1508
+ setTextBlock(httpModalBody, "Request", {
1509
+ query: payload.request?.query || {},
1510
+ body: payload.request?.body ?? null
1511
+ });
1512
+ if (payload.response?.omitted) {
1513
+ setTextBlock(httpModalBody, "Response note", payload.response.omitted);
1514
+ }
1515
+ setTextBlock(httpModalBody, "Response", {
1516
+ status: payload.response?.status ?? payload.status,
1517
+ body: payload.response?.body ?? null
1518
+ });
1519
+ httpModalBackdrop.hidden = false;
1520
+ }
1521
+
1522
+ function closeHttpModal() {
1523
+ if (!hasHttpModal) {
1524
+ return;
1525
+ }
1526
+ httpModalBackdrop.hidden = true;
1527
+ httpModalBody.innerHTML = "";
1528
+ }
1529
+
1530
+ function parseTracersFromUrl() {
1531
+ const params = new URLSearchParams(window.location.search);
1532
+ const values = [...params.getAll("tracers"), ...params.getAll("tracer")];
1533
+ const tracers = [];
1534
+ const seen = new Set();
1535
+ for (const rawValue of values) {
1536
+ const trimmed = String(rawValue || "").trim();
1537
+ if (!trimmed) {
1538
+ continue;
1539
+ }
1540
+ const inner = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
1541
+ for (const part of inner.split(",")) {
1542
+ const tracer = part.trim().replace(/^["']|["']$/g, "");
1543
+ if (!tracer || seen.has(tracer)) {
1544
+ continue;
1545
+ }
1546
+ seen.add(tracer);
1547
+ tracers.push(tracer);
1548
+ }
1549
+ }
1550
+ return tracers;
1551
+ }
1552
+
1553
+ function makeTracerId() {
1554
+ const suffix = (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function"
1555
+ ? globalThis.crypto.randomUUID().replace(/-/g, "").slice(-8)
1556
+ : Math.random().toString(16).slice(2, 10).padEnd(8, "0").slice(0, 8));
1557
+ return ${JSON.stringify(input.template.slugId)}
1558
+ .replace(/_/g, "-")
1559
+ .replace(/[^a-zA-Z0-9-]/g, "-")
1560
+ .toLowerCase()
1561
+ .replace(/-+/g, "-")
1562
+ .replace(/^-|-$/g, "") + "-" + suffix;
1563
+ }
1564
+
1565
+ function syncTracerUrl(tracers) {
1566
+ const url = new URL(window.location.href);
1567
+ if (tracers.length) {
1568
+ url.searchParams.set("tracers", tracers.join(","));
1569
+ } else {
1570
+ url.searchParams.delete("tracers");
1571
+ }
1572
+ window.history.replaceState({}, "", url.toString());
1573
+ window.dispatchEvent(new CustomEvent("vidfarm:editor-tracers-changed", {
1574
+ detail: { tracers }
1575
+ }));
1576
+ }
1577
+
1578
+ const tracerStore = window.__vidfarmEditorTracers || (() => {
1579
+ let listeners = [];
1580
+ const notify = () => {
1581
+ const nextTracers = parseTracersFromUrl();
1582
+ for (const listener of listeners) {
1583
+ listener(nextTracers);
1584
+ }
1585
+ };
1586
+ return {
1587
+ get() {
1588
+ return parseTracersFromUrl();
1589
+ },
1590
+ set(tracers) {
1591
+ const normalized = [...new Set((Array.isArray(tracers) ? tracers : []).map((value) => String(value || "").trim()).filter(Boolean))];
1592
+ syncTracerUrl(normalized);
1593
+ notify();
1594
+ },
1595
+ add(tracer) {
1596
+ const nextTracer = String(tracer || "").trim() || makeTracerId();
1597
+ const current = this.get().filter((entry) => entry !== nextTracer);
1598
+ this.set([nextTracer, ...current]);
1599
+ return nextTracer;
1600
+ },
1601
+ remove(tracer) {
1602
+ const current = this.get().filter((entry) => entry !== tracer);
1603
+ this.set(current);
1604
+ },
1605
+ activate(tracer) {
1606
+ const current = this.get();
1607
+ if (!current.includes(tracer)) {
1608
+ this.add(tracer);
1609
+ return;
1610
+ }
1611
+ this.set([tracer, ...current.filter((entry) => entry !== tracer)]);
1612
+ },
1613
+ subscribe(listener) {
1614
+ listeners.push(listener);
1615
+ return () => {
1616
+ listeners = listeners.filter((entry) => entry !== listener);
1617
+ };
1618
+ }
1619
+ };
1620
+ })();
1621
+ window.__vidfarmEditorTracers = tracerStore;
1622
+
1623
+ function openTracerModal() {
1624
+ if (!hasTracerModal) {
1625
+ return;
1626
+ }
1627
+ modalBackdrop.hidden = false;
1628
+ modalBackdrop.setAttribute("data-open", "true");
1629
+ modalError.textContent = "";
1630
+ modalInput.value = "";
1631
+ window.requestAnimationFrame(() => modalInput.focus());
1632
+ }
1633
+
1634
+ function closeTracerModal() {
1635
+ if (!hasTracerModal) {
1636
+ return;
1637
+ }
1638
+ modalBackdrop.setAttribute("data-open", "false");
1639
+ modalBackdrop.hidden = true;
1640
+ modalError.textContent = "";
1641
+ }
1642
+
1643
+ function submitTracerModal() {
1644
+ if (!hasTracerModal) {
1645
+ return;
1646
+ }
1647
+ const tracer = modalInput.value.trim();
1648
+ if (!tracer) {
1649
+ modalError.textContent = "Enter a tracer id.";
1650
+ modalInput.focus();
1651
+ return;
1652
+ }
1653
+ tracerStore.add(tracer);
1654
+ closeTracerModal();
1655
+ }
1656
+
1657
+ function renderTracerChips(tracers) {
1658
+ tracerChipRow.innerHTML = tracers.length
1659
+ ? tracers.map((tracer) => '<span class="editor-history-chip">' + escapeHtml(tracer) + '</span>').join("")
1660
+ : '<span class="editor-history-chip is-empty">No tracer selected yet</span>';
1661
+ }
1662
+
1663
+ function renderEmpty(message) {
1664
+ historyRoot.innerHTML = '<div class="editor-history-empty">' + escapeHtml(message) + '</div>';
1665
+ }
1666
+
1667
+ function renderRefreshIcon() {
1668
+ return '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
1669
+ '<path d="M21 12a9 9 0 0 1-15.1 6.6L3 16"></path>' +
1670
+ '<path d="M3 21v-5h5"></path>' +
1671
+ '<path d="M3 12a9 9 0 0 1 15.1-6.6L21 8"></path>' +
1672
+ '<path d="M21 3v5h-5"></path>' +
1673
+ '</svg>';
1674
+ }
1675
+
1676
+ function getPreviewKind(url) {
1677
+ if (/\\.(png|jpe?g|gif|webp|avif|svg)(\\?|#|$)/i.test(url)) {
1678
+ return "image";
1679
+ }
1680
+ if (/\\.(mp4|webm|ogg|mov|m4v)(\\?|#|$)/i.test(url)) {
1681
+ return "video";
1682
+ }
1683
+ return "file";
1684
+ }
1685
+
1686
+ function isPreviewableMediaUrl(url) {
1687
+ return getPreviewKind(url) !== "file";
1688
+ }
1689
+
1690
+ function openMediaModal(url, kind, label) {
1691
+ if (!hasMediaModal) {
1692
+ window.open(url, "_blank", "noopener,noreferrer");
1693
+ return;
1694
+ }
1695
+ mediaModalTitle.textContent = label;
1696
+ mediaModalSource.href = url;
1697
+ if (kind === "image") {
1698
+ mediaModalBody.innerHTML = '<img src="' + escapeHtml(url) + '" alt="' + escapeHtml(label) + '" />';
1699
+ } else if (kind === "video") {
1700
+ mediaModalBody.innerHTML = '<video src="' + escapeHtml(url) + '" controls autoplay playsinline preload="metadata"></video>';
1701
+ } else {
1702
+ mediaModalBody.innerHTML = '<a class="editor-history-button is-primary" href="' + escapeHtml(url) + '" target="_blank" rel="noreferrer">Open file</a>';
1703
+ }
1704
+ mediaModalBackdrop.hidden = false;
1705
+ mediaModalBackdrop.setAttribute("data-open", "true");
1706
+ }
1707
+
1708
+ function closeMediaModal() {
1709
+ if (!hasMediaModal) {
1710
+ return;
1711
+ }
1712
+ mediaModalBackdrop.setAttribute("data-open", "false");
1713
+ mediaModalBackdrop.hidden = true;
1714
+ mediaModalSource.href = "#";
1715
+ mediaModalBody.innerHTML = "";
1716
+ }
1717
+
1718
+ function renderPreviewLinks(urls) {
1719
+ if (!Array.isArray(urls) || !urls.length) {
1720
+ return "";
1721
+ }
1722
+ return '<div class="editor-history-card-previews">' + urls.map((url, index) => (
1723
+ (() => {
1724
+ const kind = getPreviewKind(url);
1725
+ const label = 'Output ' + (index + 1);
1726
+ if (kind === "image") {
1727
+ return '<button type="button" class="editor-history-card-preview" data-preview-url="' + escapeHtml(url) + '" data-preview-kind="' + kind + '" data-preview-label="' + escapeHtml(label) + '">' +
1728
+ '<img src="' + escapeHtml(url) + '" alt="' + escapeHtml(label) + '" loading="lazy" decoding="async" />' +
1729
+ '<span class="editor-history-card-preview-label">' + escapeHtml(label) + '</span>' +
1730
+ '</button>';
1731
+ }
1732
+ if (kind === "video") {
1733
+ return '<button type="button" class="editor-history-card-preview" data-preview-url="' + escapeHtml(url) + '" data-preview-kind="' + kind + '" data-preview-label="' + escapeHtml(label) + '">' +
1734
+ '<video src="' + escapeHtml(url) + '" muted playsinline preload="metadata" aria-hidden="true"></video>' +
1735
+ '<span class="editor-history-video-play" aria-hidden="true"></span>' +
1736
+ '<span class="editor-history-card-preview-label">' + escapeHtml(label) + '</span>' +
1737
+ '</button>';
1738
+ }
1739
+ return '<button type="button" class="editor-history-card-preview" data-preview-url="' + escapeHtml(url) + '" data-preview-kind="' + kind + '" data-preview-label="' + escapeHtml(label) + '">' +
1740
+ '<span class="editor-history-card-preview-fallback">' + escapeHtml(label) + '</span>' +
1741
+ '</button>';
1742
+ })()
1743
+ )).join("") + '</div>';
1744
+ }
1745
+
1746
+ function collectGroupPreviewUrls(entries) {
1747
+ const seen = new Set();
1748
+ const urls = [];
1749
+ for (const entry of entries) {
1750
+ const candidates = entry.outputMediaUrls && entry.outputMediaUrls.length ? entry.outputMediaUrls : entry.mediaUrls;
1751
+ if (!Array.isArray(candidates)) {
1752
+ continue;
1753
+ }
1754
+ for (const url of candidates) {
1755
+ if (!url || seen.has(url) || !isPreviewableMediaUrl(url)) {
1756
+ continue;
1757
+ }
1758
+ seen.add(url);
1759
+ urls.push(url);
1760
+ }
1761
+ }
1762
+ return urls;
1763
+ }
1764
+
1765
+ function renderGroupPreviewStrip(entries, groupKey) {
1766
+ const urls = collectGroupPreviewUrls(entries);
1767
+ if (!urls.length) {
1768
+ return "";
1769
+ }
1770
+ const isExpanded = expandedPreviewGroups.has(groupKey);
1771
+ const visibleUrls = isExpanded ? urls : urls.slice(0, 5);
1772
+ const visible = visibleUrls.map((url) => {
1773
+ const kind = getPreviewKind(url);
1774
+ if (kind === "video") {
1775
+ const label = 'Output preview';
1776
+ return '<button type="button" class="editor-history-group-preview" data-preview-url="' + escapeHtml(url) + '" data-preview-kind="video" data-preview-label="' + escapeHtml(label) + '" aria-label="Preview video output">' +
1777
+ '<video src="' + escapeHtml(url) + '" muted playsinline preload="metadata" aria-hidden="true"></video>' +
1778
+ '<span class="editor-history-video-play" aria-hidden="true"></span>' +
1779
+ '</button>';
1780
+ }
1781
+ if (kind === "image") {
1782
+ const label = 'Output preview';
1783
+ return '<button type="button" class="editor-history-group-preview" data-preview-url="' + escapeHtml(url) + '" data-preview-kind="image" data-preview-label="' + escapeHtml(label) + '" aria-label="Preview image output">' +
1784
+ '<img src="' + escapeHtml(url) + '" alt="" loading="lazy" decoding="async" />' +
1785
+ '</button>';
1786
+ }
1787
+ return "";
1788
+ }).join("");
1789
+ const more = !isExpanded && urls.length > visibleUrls.length
1790
+ ? '<button type="button" class="editor-history-group-preview-more" data-editor-expand-media="' + escapeHtml(groupKey) + '" aria-label="Show all media previews">+' + escapeHtml(urls.length - visibleUrls.length) + '</button>'
1791
+ : "";
1792
+ return '<div class="editor-history-group-previews' + (isExpanded ? ' is-expanded' : '') + '" aria-label="Generated media previews">' + visible + more + '</div>';
1793
+ }
1794
+
1795
+ function renderSample(title, payload, omittedReason) {
1796
+ return '<section class="editor-history-sample">' +
1797
+ '<h4>' + escapeHtml(title) + '</h4>' +
1798
+ (omittedReason ? '<div class="editor-history-sample-note">' + escapeHtml(omittedReason) + '</div>' : '') +
1799
+ '<pre>' + escapeHtml(formatJson(payload)) + '</pre>' +
1800
+ '</section>';
1801
+ }
1802
+
1803
+ function renderEntry(entry) {
1804
+ return '<article class="editor-history-card">' +
1805
+ renderHttpAction(entry) +
1806
+ '<div class="editor-history-card-foot">' +
1807
+ '<span>' + escapeHtml(new Date(entry.createdAt).toLocaleString()) + '</span>' +
1808
+ '</div>' +
1809
+ renderPreviewLinks(entry.outputMediaUrls && entry.outputMediaUrls.length ? entry.outputMediaUrls : entry.mediaUrls) +
1810
+ '</article>';
1811
+ }
1812
+
1813
+ function hasGeneratedTracerSuffix(tracer) {
1814
+ return /-[a-z0-9]{8}$/i.test(String(tracer || "").trim());
1815
+ }
1816
+
1817
+ function doesTracerMatchSelection(entryTracer, selectedTracer) {
1818
+ const normalizedEntry = String(entryTracer || "").trim();
1819
+ const normalizedSelected = String(selectedTracer || "").trim();
1820
+ if (!normalizedEntry || !normalizedSelected) {
1821
+ return false;
1822
+ }
1823
+ if (normalizedEntry === normalizedSelected) {
1824
+ return true;
1825
+ }
1826
+ return normalizedEntry.startsWith(normalizedSelected + "-") && hasGeneratedTracerSuffix(normalizedEntry);
1827
+ }
1828
+
1829
+ async function copyText(value) {
1830
+ if (navigator.clipboard && window.isSecureContext) {
1831
+ try {
1832
+ await navigator.clipboard.writeText(value);
1833
+ return true;
1834
+ } catch {
1835
+ // Fall back to the textarea path when browser permissions block clipboard.writeText.
1836
+ }
1837
+ }
1838
+ const field = document.createElement("textarea");
1839
+ field.value = value;
1840
+ field.setAttribute("readonly", "");
1841
+ field.style.position = "fixed";
1842
+ field.style.opacity = "0";
1843
+ document.body.appendChild(field);
1844
+ field.select();
1845
+ let copied = false;
1846
+ try {
1847
+ copied = document.execCommand("copy");
1848
+ } finally {
1849
+ field.remove();
1850
+ }
1851
+ return copied;
1852
+ }
1853
+
1854
+ const copyFeedbackTimers = new WeakMap();
1855
+
1856
+ function clearCopyFeedback(button) {
1857
+ const previousTimer = copyFeedbackTimers.get(button);
1858
+ if (previousTimer) {
1859
+ window.clearTimeout(previousTimer);
1860
+ }
1861
+ copyFeedbackTimers.delete(button);
1862
+ button.removeAttribute("data-copying");
1863
+ button.removeAttribute("data-copied");
1864
+ button.removeAttribute("data-copy-error");
1865
+ }
1866
+
1867
+ function resetCopyFeedback(button, label) {
1868
+ button.removeAttribute("data-copying");
1869
+ button.removeAttribute("data-copied");
1870
+ button.removeAttribute("data-copy-error");
1871
+ button.setAttribute("aria-label", "Copy " + label);
1872
+ button.setAttribute("title", "Copy " + label);
1873
+ copyFeedbackTimers.delete(button);
1874
+ }
1875
+
1876
+ function showCopyingFeedback(button, label) {
1877
+ clearCopyFeedback(button);
1878
+ button.setAttribute("data-copying", "true");
1879
+ button.setAttribute("aria-label", "Copying " + label);
1880
+ button.setAttribute("title", "Copying");
1881
+ }
1882
+
1883
+ function showCopyFeedback(button, label) {
1884
+ clearCopyFeedback(button);
1885
+ button.setAttribute("data-copied", "true");
1886
+ button.setAttribute("aria-label", "Copied " + label);
1887
+ button.setAttribute("title", "Copied");
1888
+ const timer = window.setTimeout(() => {
1889
+ resetCopyFeedback(button, label);
1890
+ }, 1400);
1891
+ copyFeedbackTimers.set(button, timer);
1892
+ }
1893
+
1894
+ function showCopyErrorFeedback(button, label) {
1895
+ clearCopyFeedback(button);
1896
+ button.setAttribute("data-copy-error", "true");
1897
+ button.setAttribute("aria-label", "Copy failed for " + label);
1898
+ button.setAttribute("title", "Copy failed");
1899
+ const timer = window.setTimeout(() => {
1900
+ resetCopyFeedback(button, label);
1901
+ }, 1800);
1902
+ copyFeedbackTimers.set(button, timer);
1903
+ }
1904
+
1905
+ function renderGroups(entries, tracers) {
1906
+ if (!tracers.length) {
1907
+ renderEmpty("Use Copilot to start or select a tracer, then its grouped POST history will appear here.");
1908
+ return;
1909
+ }
1910
+
1911
+ const grouped = new Map();
1912
+ for (const entry of entries) {
1913
+ const key = tracers.find((tracer) => doesTracerMatchSelection(entry.tracer, tracer)) || entry.tracer || "No tracer";
1914
+ const existing = grouped.get(key) || [];
1915
+ existing.push(entry);
1916
+ grouped.set(key, existing);
1917
+ }
1918
+
1919
+ historyRoot.innerHTML = tracers.map((tracer, index) => {
1920
+ const groupEntries = grouped.get(tracer) || [];
1921
+ const activeRefresh = index === 0
1922
+ ? '<button type="button" class="editor-history-button editor-history-refresh" data-editor-history-refresh="' + escapeHtml(tracer) + '" aria-label="Refresh tracer history" title="Refresh tracer history">' + renderRefreshIcon() + '</button>'
1923
+ : '';
1924
+ const menu = '<span class="editor-history-menu-wrap" data-editor-history-menu-wrap>' +
1925
+ '<button type="button" class="editor-history-button editor-history-menu-button" data-editor-history-menu-toggle="' + escapeHtml(tracer) + '" aria-label="Tracer actions" title="Tracer actions">...</button>' +
1926
+ '<span class="editor-history-menu" role="menu">' +
1927
+ '<button type="button" data-editor-history-copy="' + escapeHtml(tracer) + '">Copy Tracer</button>' +
1928
+ '<button type="button" data-editor-history-rename="' + escapeHtml(tracer) + '">Rename tracer</button>' +
1929
+ '<button type="button" data-editor-history-remove="' + escapeHtml(tracer) + '">Remove from view</button>' +
1930
+ '<button type="button" data-editor-history-archive="' + escapeHtml(tracer) + '" data-danger="true">Archive tracer</button>' +
1931
+ '</span>' +
1932
+ '</span>';
1933
+ const callsLabel = groupEntries.length
1934
+ ? escapeHtml(groupEntries.length) + ' POST call' + (groupEntries.length === 1 ? '' : 's')
1935
+ : 'No POST calls yet';
1936
+ return (
1937
+ '<details class="editor-history-group"' + (index === 0 ? ' open' : '') + '>' +
1938
+ '<summary>' +
1939
+ '<div class="editor-history-group-main">' +
1940
+ '<h3>' + escapeHtml(tracer) + '</h3>' +
1941
+ '<p>' + callsLabel + '</p>' +
1942
+ renderGroupPreviewStrip(groupEntries, tracer) +
1943
+ '</div>' +
1944
+ '<div class="editor-history-group-actions">' +
1945
+ activeRefresh +
1946
+ menu +
1947
+ '<span class="editor-history-group-toggle">Toggle</span>' +
1948
+ '</div>' +
1949
+ '</summary>' +
1950
+ '<div class="editor-history-group-body">' +
1951
+ (groupEntries.length
1952
+ ? groupEntries.map(renderEntry).join("")
1953
+ : '<div class="editor-history-empty">No POST history exists for this tracer yet.</div>') +
1954
+ '</div>' +
1955
+ '</details>'
1956
+ )}).join("");
1957
+ }
1958
+
1959
+ async function fetchHistoryPayload(endpoint) {
1960
+ const response = await fetch(endpoint.toString(), { credentials: "same-origin" });
1961
+ let payload = null;
1962
+ try {
1963
+ payload = await response.json();
1964
+ } catch {
1965
+ payload = null;
1966
+ }
1967
+ if (!response.ok) {
1968
+ const message = payload && typeof payload.error === "string"
1969
+ ? payload.error
1970
+ : "Unable to load POST history.";
1971
+ throw new Error(message + " (" + response.status + ")");
1972
+ }
1973
+ return payload || {};
1974
+ }
1975
+
1976
+ async function loadFallbackHistory(selectedTracers) {
1977
+ if (!boot.fallbackHistoryEndpoint) {
1978
+ throw new Error("No fallback history endpoint is configured.");
1979
+ }
1980
+ const byId = new Map();
1981
+ for (const tracer of selectedTracers) {
1982
+ const endpoint = new URL(boot.fallbackHistoryEndpoint, window.location.origin);
1983
+ endpoint.searchParams.set("tracer", tracer);
1984
+ const payload = await fetchHistoryPayload(endpoint);
1985
+ for (const entry of Array.isArray(payload.entries) ? payload.entries : []) {
1986
+ byId.set(entry.id, entry);
1987
+ }
1988
+ }
1989
+ return { entries: [...byId.values()] };
1990
+ }
1991
+
1992
+ async function loadHistory(options) {
1993
+ const showLoading = Boolean(options && options.showLoading);
1994
+ const triggerButton = options && options.triggerButton instanceof HTMLButtonElement ? options.triggerButton : null;
1995
+ const selectedTracers = tracerStore.get();
1996
+ renderTracerChips(selectedTracers);
1997
+ if (!selectedTracers.length) {
1998
+ renderEmpty("No tracer selected yet.");
1999
+ return;
2000
+ }
2001
+ if (!boot.isLoggedIn) {
2002
+ renderEmpty("Sign in to load tracer-scoped POST history.");
2003
+ return;
2004
+ }
2005
+
2006
+ const endpoint = new URL(boot.historyEndpoint, window.location.origin);
2007
+ if (selectedTracers.length) {
2008
+ endpoint.searchParams.set("tracers", selectedTracers.join(","));
2009
+ } else {
2010
+ endpoint.searchParams.delete("tracers");
2011
+ }
2012
+
2013
+ if (triggerButton) {
2014
+ triggerButton.disabled = true;
2015
+ triggerButton.setAttribute("data-loading", "true");
2016
+ } else if (showLoading) {
2017
+ historyRoot.innerHTML = '<div class="editor-history-empty">Loading POST history…</div>';
2018
+ }
2019
+ try {
2020
+ let payload;
2021
+ try {
2022
+ payload = await fetchHistoryPayload(endpoint);
2023
+ } catch (primaryError) {
2024
+ console.warn("Editor-scoped history failed; trying job-runs history fallback.", primaryError);
2025
+ payload = await loadFallbackHistory(selectedTracers);
2026
+ }
2027
+ const entries = Array.isArray(payload.entries) ? payload.entries : [];
2028
+ lastHistoryEntries = entries;
2029
+ lastSelectedTracers = selectedTracers;
2030
+ renderGroups(entries, selectedTracers);
2031
+ } catch (error) {
2032
+ console.error(error);
2033
+ renderEmpty("Unable to load tracer-scoped POST history" + (error instanceof Error && error.message ? ": " + error.message : "."));
2034
+ } finally {
2035
+ if (triggerButton) {
2036
+ triggerButton.disabled = false;
2037
+ triggerButton.setAttribute("data-loading", "false");
2038
+ }
2039
+ }
2040
+ }
2041
+
2042
+ async function renameStoredTracer(fromTracer, toTracer) {
2043
+ const normalizedFrom = String(fromTracer || "").trim();
2044
+ const normalizedTo = String(toTracer || "").trim();
2045
+ if (!normalizedFrom || !normalizedTo || normalizedFrom === normalizedTo) {
2046
+ return;
2047
+ }
2048
+ const response = await fetch(boot.tracerEndpoint, {
2049
+ method: "PATCH",
2050
+ credentials: "same-origin",
2051
+ headers: { "content-type": "application/json" },
2052
+ body: JSON.stringify({
2053
+ from_tracer: normalizedFrom,
2054
+ to_tracer: normalizedTo
2055
+ })
2056
+ });
2057
+ if (!response.ok) {
2058
+ throw new Error("Unable to update tracer.");
2059
+ }
2060
+ const nextTracers = tracerStore.get().map((entry) => entry === normalizedFrom ? normalizedTo : entry);
2061
+ tracerStore.set(nextTracers);
2062
+ await loadHistory({ showLoading: true });
2063
+ }
2064
+
2065
+ loadHistory({ showLoading: true });
2066
+ window.addEventListener("popstate", () => loadHistory({ showLoading: true }));
2067
+ window.addEventListener("vidfarm:editor-tracers-changed", () => loadHistory());
2068
+ if (hasTracerModal) {
2069
+ addTracerButton.addEventListener("click", () => openTracerModal());
2070
+ modalSubmit.addEventListener("click", () => submitTracerModal());
2071
+ modalCancel.addEventListener("click", () => closeTracerModal());
2072
+ modalInput.addEventListener("keydown", (event) => {
2073
+ if (event.key === "Enter") {
2074
+ event.preventDefault();
2075
+ submitTracerModal();
2076
+ return;
2077
+ }
2078
+ if (event.key === "Escape") {
2079
+ event.preventDefault();
2080
+ closeTracerModal();
2081
+ }
2082
+ });
2083
+ modalBackdrop.addEventListener("click", (event) => {
2084
+ if (event.target === modalBackdrop) {
2085
+ closeTracerModal();
2086
+ }
2087
+ });
2088
+ }
2089
+ if (hasMediaModal) {
2090
+ mediaModalClose.addEventListener("click", () => closeMediaModal());
2091
+ mediaModalBackdrop.addEventListener("click", (event) => {
2092
+ if (event.target === mediaModalBackdrop) {
2093
+ closeMediaModal();
2094
+ }
2095
+ });
2096
+ }
2097
+ if (hasHttpModal) {
2098
+ httpModalClose.addEventListener("click", () => closeHttpModal());
2099
+ httpModalBackdrop.addEventListener("click", (event) => {
2100
+ if (event.target === httpModalBackdrop) {
2101
+ closeHttpModal();
2102
+ }
2103
+ });
2104
+ }
2105
+ window.addEventListener("keydown", (event) => {
2106
+ if (event.key === "Escape") {
2107
+ if (hasHttpModal && httpModalBackdrop.hidden === false) {
2108
+ closeHttpModal();
2109
+ return;
2110
+ }
2111
+ if (hasMediaModal && mediaModalBackdrop.getAttribute("data-open") === "true") {
2112
+ closeMediaModal();
2113
+ return;
2114
+ }
2115
+ if (hasTracerModal && modalBackdrop.getAttribute("data-open") === "true") {
2116
+ closeTracerModal();
2117
+ }
2118
+ }
2119
+ });
2120
+ historyRoot.addEventListener("click", (event) => {
2121
+ const target = event.target;
2122
+ if (!(target instanceof Element)) {
2123
+ return;
2124
+ }
2125
+ const previewButton = target.closest("[data-preview-url]");
2126
+ if (previewButton instanceof HTMLButtonElement) {
2127
+ event.preventDefault();
2128
+ event.stopPropagation();
2129
+ const url = previewButton.getAttribute("data-preview-url") || "";
2130
+ const kind = previewButton.getAttribute("data-preview-kind") || "file";
2131
+ const label = previewButton.getAttribute("data-preview-label") || "Output preview";
2132
+ if (url) {
2133
+ openMediaModal(url, kind, label);
2134
+ }
2135
+ return;
2136
+ }
2137
+ const expandMediaButton = target.closest("[data-editor-expand-media]");
2138
+ if (expandMediaButton instanceof HTMLButtonElement) {
2139
+ event.preventDefault();
2140
+ event.stopPropagation();
2141
+ const key = expandMediaButton.getAttribute("data-editor-expand-media") || "";
2142
+ if (key) {
2143
+ expandedPreviewGroups.add(key);
2144
+ renderGroups(lastHistoryEntries, lastSelectedTracers);
2145
+ }
2146
+ return;
2147
+ }
2148
+ const httpDetailButton = target.closest("[data-editor-http-detail]");
2149
+ if (httpDetailButton instanceof HTMLButtonElement) {
2150
+ event.preventDefault();
2151
+ event.stopPropagation();
2152
+ try {
2153
+ openHttpModal(JSON.parse(decodeURIComponent(httpDetailButton.getAttribute("data-editor-http-detail") || "")));
2154
+ } catch (error) {
2155
+ console.error(error);
2156
+ }
2157
+ return;
2158
+ }
2159
+ const refreshButton = target.closest("[data-editor-history-refresh]");
2160
+ if (refreshButton instanceof HTMLButtonElement) {
2161
+ event.preventDefault();
2162
+ event.stopPropagation();
2163
+ void loadHistory({ triggerButton: refreshButton });
2164
+ return;
2165
+ }
2166
+ const menuToggle = target.closest("[data-editor-history-menu-toggle]");
2167
+ if (menuToggle instanceof HTMLButtonElement) {
2168
+ event.preventDefault();
2169
+ event.stopPropagation();
2170
+ const wrap = menuToggle.closest("[data-editor-history-menu-wrap]");
2171
+ const isOpen = wrap instanceof HTMLElement && wrap.getAttribute("data-open") === "true";
2172
+ document.querySelectorAll("[data-editor-history-menu-wrap]").forEach((entry) => entry.removeAttribute("data-open"));
2173
+ if (wrap instanceof HTMLElement && !isOpen) {
2174
+ wrap.setAttribute("data-open", "true");
2175
+ }
2176
+ return;
2177
+ }
2178
+ const copyButton = target.closest("[data-editor-history-copy]");
2179
+ if (copyButton instanceof HTMLButtonElement) {
2180
+ event.preventDefault();
2181
+ event.stopPropagation();
2182
+ const tracer = copyButton.getAttribute("data-editor-history-copy") || "";
2183
+ document.querySelectorAll("[data-editor-history-menu-wrap]").forEach((entry) => entry.removeAttribute("data-open"));
2184
+ if (!tracer) {
2185
+ return;
2186
+ }
2187
+ void copyText(tracer).then((copied) => {
2188
+ if (!copied) {
2189
+ window.prompt("Copy tracer", tracer);
2190
+ }
2191
+ }).catch((error) => {
2192
+ console.error(error);
2193
+ window.prompt("Copy tracer", tracer);
2194
+ });
2195
+ return;
2196
+ }
2197
+ const renameButton = target.closest("[data-editor-history-rename]");
2198
+ if (renameButton instanceof HTMLButtonElement) {
2199
+ event.preventDefault();
2200
+ event.stopPropagation();
2201
+ const tracer = renameButton.getAttribute("data-editor-history-rename") || "";
2202
+ document.querySelectorAll("[data-editor-history-menu-wrap]").forEach((entry) => entry.removeAttribute("data-open"));
2203
+ const nextTracer = window.prompt("Rename tracer", tracer);
2204
+ if (nextTracer === null || !nextTracer.trim() || nextTracer.trim() === tracer) {
2205
+ return;
2206
+ }
2207
+ void renameStoredTracer(tracer, nextTracer.trim()).catch((error) => {
2208
+ console.error(error);
2209
+ window.alert("Unable to rename tracer.");
2210
+ });
2211
+ return;
2212
+ }
2213
+ const archiveButton = target.closest("[data-editor-history-archive]");
2214
+ if (archiveButton instanceof HTMLButtonElement) {
2215
+ event.preventDefault();
2216
+ event.stopPropagation();
2217
+ const tracer = archiveButton.getAttribute("data-editor-history-archive") || "";
2218
+ document.querySelectorAll("[data-editor-history-menu-wrap]").forEach((entry) => entry.removeAttribute("data-open"));
2219
+ if (!tracer || tracer.startsWith("archived_")) {
2220
+ return;
2221
+ }
2222
+ const confirmed = window.confirm('Archive "' + tracer + '"? This renames its POST logs and jobs to "archived_' + tracer + '".');
2223
+ if (confirmed) {
2224
+ void renameStoredTracer(tracer, "archived_" + tracer).catch((error) => {
2225
+ console.error(error);
2226
+ window.alert("Unable to archive tracer.");
2227
+ });
2228
+ }
2229
+ return;
2230
+ }
2231
+ const removeButton = target.closest("[data-editor-history-remove]");
2232
+ if (removeButton instanceof HTMLButtonElement) {
2233
+ event.preventDefault();
2234
+ event.stopPropagation();
2235
+ const tracer = removeButton.getAttribute("data-editor-history-remove") || "";
2236
+ document.querySelectorAll("[data-editor-history-menu-wrap]").forEach((entry) => entry.removeAttribute("data-open"));
2237
+ if (!tracer) {
2238
+ return;
2239
+ }
2240
+ const confirmed = window.confirm('Remove "' + tracer + '" from this page view? This does not delete its runs. You can still find them in Job Runs.');
2241
+ if (confirmed) {
2242
+ tracerStore.remove(tracer);
2243
+ }
2244
+ }
2245
+ });
2246
+ document.addEventListener("click", (event) => {
2247
+ const target = event.target;
2248
+ if (!(target instanceof Element)) {
2249
+ return;
2250
+ }
2251
+ const copyButton = target.closest("[data-editor-copy-content]");
2252
+ if (!(copyButton instanceof HTMLButtonElement)) {
2253
+ return;
2254
+ }
2255
+ event.preventDefault();
2256
+ const value = copyButton.getAttribute("data-editor-copy-content") || "";
2257
+ const label = copyButton.getAttribute("data-editor-copy-label") || "Copied text";
2258
+ if (!value) {
2259
+ return;
2260
+ }
2261
+ showCopyingFeedback(copyButton, label);
2262
+ void copyText(value).then((copied) => {
2263
+ if (!copied) {
2264
+ showCopyErrorFeedback(copyButton, label);
2265
+ window.prompt(label, value);
2266
+ return;
2267
+ }
2268
+ showCopyFeedback(copyButton, label);
2269
+ }).catch((error) => {
2270
+ console.error(error);
2271
+ showCopyErrorFeedback(copyButton, label);
2272
+ window.prompt(label, value);
2273
+ });
2274
+ });
2275
+ })();
2276
+ </script>
2277
+ `
2278
+ });
2279
+ }
2280
+ function renderPreview(url, alt) {
2281
+ if (/\.(mp4|webm|ogg|mov)(\?|#|$)/i.test(url)) {
2282
+ return `<video class="editor-preview" src="${escapeAttribute(url)}" controls playsinline preload="metadata" aria-label="${escapeAttribute(alt)}"></video>`;
2283
+ }
2284
+ return `<img class="editor-preview" src="${escapeAttribute(url)}" alt="${escapeAttribute(alt)}" loading="lazy" decoding="async" />`;
2285
+ }
2286
+ function renderQuickstartTextarea(label, value, rows) {
2287
+ const text = value.trim() || "Not configured for this template yet.";
2288
+ return `
2289
+ <div class="editor-quickstart-field">
2290
+ <div class="editor-quickstart-field-head">
2291
+ <span>${escapeHtml(label)}</span>
2292
+ <button
2293
+ type="button"
2294
+ class="editor-quickstart-copy"
2295
+ data-editor-copy-content="${escapeAttribute(text)}"
2296
+ data-editor-copy-label="${escapeAttribute(label)}"
2297
+ aria-label="Copy ${escapeAttribute(label)}"
2298
+ title="Copy ${escapeAttribute(label)}"
2299
+ >
2300
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
2301
+ <rect x="9" y="9" width="10" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="2"></rect>
2302
+ <path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
2303
+ </svg>
2304
+ </button>
2305
+ </div>
2306
+ <textarea class="editor-quickstart-textarea" readonly rows="${rows}" spellcheck="false" aria-label="${escapeAttribute(label)}">${escapeHtml(text)}</textarea>
2307
+ </div>
2308
+ `;
2309
+ }