@mevdragon/vidfarm-devcli 0.2.13 → 0.2.14

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 (42) hide show
  1. package/dist/src/account-pages-legacy.js +7 -5
  2. package/dist/src/account-pages-legacy.js.map +1 -1
  3. package/dist/src/account-pages.js +1 -10
  4. package/dist/src/account-pages.js.map +1 -1
  5. package/dist/src/app.js +106 -418
  6. package/dist/src/app.js.map +1 -1
  7. package/dist/src/cli.js +5 -0
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/dev-app-legacy.js +1 -1
  10. package/dist/src/dev-serve.js +169 -0
  11. package/dist/src/dev-serve.js.map +1 -0
  12. package/dist/src/editor-chat.js +10 -9
  13. package/dist/src/editor-chat.js.map +1 -1
  14. package/dist/src/editor-dark-theme.js +1128 -0
  15. package/dist/src/editor-dark-theme.js.map +1 -0
  16. package/dist/src/frontend/homepage-view.js +4 -4
  17. package/dist/src/frontend/homepage-view.js.map +1 -1
  18. package/dist/src/frontend/page-runtime-client.js +23 -2
  19. package/dist/src/frontend/page-runtime-client.js.map +1 -1
  20. package/dist/src/frontend/template-editor-chat.js +28 -35
  21. package/dist/src/frontend/template-editor-chat.js.map +1 -1
  22. package/dist/src/homepage.js +1 -1
  23. package/dist/src/homepage.js.map +1 -1
  24. package/dist/src/page-shell.js +27 -1105
  25. package/dist/src/page-shell.js.map +1 -1
  26. package/dist/src/primitive-registry.js +3 -3
  27. package/dist/src/primitive-registry.js.map +1 -1
  28. package/dist/src/services/fork-access.js +6 -7
  29. package/dist/src/services/fork-access.js.map +1 -1
  30. package/dist/src/services/fork-manifest.js +43 -0
  31. package/dist/src/services/fork-manifest.js.map +1 -0
  32. package/dist/src/services/serverless-records.js +2 -0
  33. package/dist/src/services/serverless-records.js.map +1 -1
  34. package/dist/src/services/storage.js +3 -2
  35. package/dist/src/services/storage.js.map +1 -1
  36. package/dist/src/template-editor-pages.js +3 -4136
  37. package/dist/src/template-editor-pages.js.map +1 -1
  38. package/dist/src/template-editor-shell.js +42 -53
  39. package/dist/src/template-editor-shell.js.map +1 -1
  40. package/package.json +1 -1
  41. package/public/assets/homepage-client-app.js +4 -4
  42. package/public/assets/page-runtime-client-app.js +29 -29
package/dist/src/app.js CHANGED
@@ -8,13 +8,14 @@ import { stepCountIs, streamText, tool } from "ai";
8
8
  import { Hono } from "hono";
9
9
  import * as Sentry from "@sentry/hono/node";
10
10
  import { z } from "zod";
11
- import { renderAgencyPage, renderCalendarPage, renderChatPage, renderJobRunsPage, renderLibraryPage, renderLoginPage, renderPricingPage, renderSettingsPage } from "./account-pages.js";
11
+ import { renderAgencyPage, renderCalendarPage, renderJobRunsPage, renderLibraryPage, renderLoginPage, renderPricingPage, renderSettingsPage } from "./account-pages.js";
12
12
  import { config } from "./config.js";
13
13
  import { serverlessRecords } from "./services/serverless-records.js";
14
14
  import { renderDevApp } from "./dev-app.js";
15
15
  import { CUSTOMER_PLAN_TIERS } from "./domain.js";
16
16
  import { sanitizeChatPayload, sanitizeHttpExchange } from "./editor-chat-history.js";
17
17
  import { assertForkAccess, resolveForkAccess, ForkAccessDeniedError, ForkNotFoundError } from "./services/fork-access.js";
18
+ import { writeForkManifest } from "./services/fork-manifest.js";
18
19
  import { renderHelpPage } from "./help-page.js";
19
20
  import { renderHomepage } from "./homepage.js";
20
21
  import { COMPOSITION_RUNTIME_SCRIPT_BODY } from "./composition-runtime.js";
@@ -41,7 +42,7 @@ import { probeMediaAsset } from "./services/media-processing.js";
41
42
  import { estimateGhostcutCostUsd, isGhostcutConfigured, mirrorGhostcutVideoToStorage, pollStatus as pollGhostcutStatus, submitSubtitleRemoval } from "./services/ghostcut.js";
42
43
  import { TemplateSourceService } from "./services/template-sources.js";
43
44
  import { TemplateSourceAlreadyRegisteredError } from "./services/template-sources.js";
44
- import { renderCustomInpaintPage, renderTemplateEditorDocsPage } from "./template-editor-pages.js";
45
+ import { renderCustomInpaintPage } from "./template-editor-pages.js";
45
46
  import { escapeAttribute, escapeHtml, escapeJsonForHtml, renderBrandLockup, renderPageShell, renderPrimaryNav } from "./page-shell.js";
46
47
  import { renderReadyPostScheduleData, renderReadyPostScheduleModal, renderReadyPostScheduleScript, renderReadyPostScheduleStyles } from "./ready-post-schedule-component.js";
47
48
  const auth = new AuthService();
@@ -560,7 +561,7 @@ function shouldInitializeTemplateRegistry(pathname) {
560
561
  || pathname === "/pricing"
561
562
  || pathname === "/discover/feed"
562
563
  || pathname.startsWith("/discover/skills/")
563
- || pathname.startsWith("/hyperframes/")
564
+ || pathname.startsWith("/editor/")
564
565
  || pathname.startsWith("/drafts/")
565
566
  || pathname === "/job-runs"
566
567
  || pathname === "/job-runs/history"
@@ -3155,7 +3156,7 @@ function buildHyperframesDiscoverTemplate(draftPath) {
3155
3156
  templateId: `hyperframes:${slugId}`,
3156
3157
  slugId,
3157
3158
  difficulty: estimateDraftDifficulty(editorLayers.length, sourceType),
3158
- viralDna: buildDraftViralSummary(compositionData) || "Draft composition with editable HyperFrames timeline layers.",
3159
+ viralDna: buildDraftViralSummary(compositionData) || "Draft composition with editable timeline layers.",
3159
3160
  previewUrl,
3160
3161
  approvedAt: readNonEmptyString(compositionData.created_at) ?? "2026-06-30T00:00:00.000Z",
3161
3162
  lambdaMemoryMb: estimateDraftLambdaMemoryMb(sourceType),
@@ -3327,7 +3328,7 @@ function buildHyperframesDraftEditorTemplate(composition) {
3327
3328
  sound_url: composition.previewUrl ?? ""
3328
3329
  },
3329
3330
  proposalGenerator: [
3330
- "Use this draft as a HyperFrames layer timeline. Edit via the Trackpad Editor composition, not imperative scene commands.",
3331
+ "Use this draft as a layer timeline. Edit via the Trackpad Editor composition, not imperative scene commands.",
3331
3332
  "",
3332
3333
  "Source assets:",
3333
3334
  sourceAssetLines || "- No source assets declared.",
@@ -3335,7 +3336,7 @@ function buildHyperframesDraftEditorTemplate(composition) {
3335
3336
  "Editable layer inventory:",
3336
3337
  layerLines || "- No layers declared.",
3337
3338
  "",
3338
- "When rendering, prefer POST /api/v1/primitives/hyperframes/render with composition_html from the draft or a composition.layers payload derived from the layer inventory."
3339
+ "When rendering, prefer POST /api/v1/primitives/timeline/render with composition_html from the draft or a composition.layers payload derived from the layer inventory."
3339
3340
  ].join("\n"),
3340
3341
  skeletonPrompt: formatHyperframesDraftJson({
3341
3342
  composition_id: composition.slugId,
@@ -3351,16 +3352,16 @@ function buildHyperframesDraftEditorTemplate(composition) {
3351
3352
  }),
3352
3353
  samplePrompts: [
3353
3354
  `Open ${composition.draftPath}/composition.html and explain the frame/layer timeline.`,
3354
- `Render ${composition.slugId} through /api/v1/primitives/hyperframes/render using the current draft composition HTML.`,
3355
+ `Render ${composition.slugId} through /api/v1/primitives/timeline/render using the current draft composition HTML.`,
3355
3356
  `Suggest where to move or trim the overlay layers without changing source media timing.`
3356
3357
  ],
3357
3358
  examples: previewExamples.length ? [previewExamples] : [],
3358
3359
  previewUrl: composition.previewUrl,
3359
- skillUrl: `/hyperframes/${encodeURIComponent(composition.slugId)}/skill`,
3360
+ skillUrl: `/editor/${encodeURIComponent(composition.slugId)}/skill`,
3360
3361
  operations: [
3361
3362
  {
3362
3363
  name: "render",
3363
- description: "Render this draft composition through the primitive HyperFrames Lambda route."
3364
+ description: "Render this draft composition through the timeline render primitive."
3364
3365
  },
3365
3366
  {
3366
3367
  name: "inspect_layers",
@@ -3391,15 +3392,15 @@ function buildHyperframesDraftSkill(composition) {
3391
3392
  editorLayers.map((layer) => `- ${layer.id}: ${layer.label}, ${layer.type}, track ${layer.track}, ${layer.start}s for ${layer.duration}s`).join("\n") || "- None declared.",
3392
3393
  "",
3393
3394
  "## Render",
3394
- "Use `POST /api/v1/primitives/hyperframes/render` with either `payload.composition_html` from the composition route or a `payload.composition.layers` object derived from composition.json."
3395
+ "Use `POST /api/v1/primitives/timeline/render` with either `payload.composition_html` from the composition route or a `payload.composition.layers` object derived from composition.json."
3395
3396
  ].join("\n");
3396
3397
  }
3397
3398
  function buildHyperframesDraftEditorDocsRoutes(composition) {
3398
3399
  return [
3399
- { method: "GET", path: `/hyperframes/${composition.slugId}/editor`, summary: "Open this draft in the shared HyperFrames timeline workspace." },
3400
- { method: "GET", path: `/hyperframes/${composition.slugId}/composition`, summary: "Read the draft composition.html with draft-relative assets resolved." },
3401
- { method: "GET", path: `/hyperframes/${composition.slugId}/composition.json`, summary: "Read the draft composition.json that drives layer/timeline metadata." },
3402
- { method: "GET", path: `/hyperframes/${composition.slugId}/skill`, summary: "Read the generated composition skill context for the Copilot." },
3400
+ { method: "GET", path: `/editor/${composition.slugId}`, summary: "Open this draft in the shared timeline editor." },
3401
+ { method: "GET", path: `/editor/${composition.slugId}/composition`, summary: "Read the draft composition.html with draft-relative assets resolved." },
3402
+ { method: "GET", path: `/editor/${composition.slugId}/composition.json`, summary: "Read the draft composition.json that drives layer/timeline metadata." },
3403
+ { method: "GET", path: `/editor/${composition.slugId}/skill`, summary: "Read the generated composition skill context for the Copilot." },
3403
3404
  ...buildPrimitiveEditorDocsRoutes()
3404
3405
  ];
3405
3406
  }
@@ -3438,32 +3439,6 @@ async function buildHyperframesDraftEditorChatBoot(input) {
3438
3439
  async function getApprovedHomepageTemplates(_c, input = {}) {
3439
3440
  return paginateHyperframesDiscoverTemplates(input);
3440
3441
  }
3441
- function buildTemplateEditorDocsRoutes(template) {
3442
- const templatePath = `${TEMPLATES_PREFIX}/${template.id}`;
3443
- return [
3444
- { method: "GET", path: `${templatePath}`, summary: "Fetch template metadata, operations, and base identifiers." },
3445
- { method: "GET", path: `${templatePath}/skill`, summary: "Fetch the template SKILL.md payload through the API wrapper." },
3446
- { method: "POST", path: `${templatePath}/config`, summary: "Persist template-specific customer config for later runs." },
3447
- ...Object.entries(template.operations).map(([name, operation]) => ({
3448
- method: "POST",
3449
- path: `${templatePath}/operations/${name}`,
3450
- summary: operation.description
3451
- })),
3452
- { method: "GET", path: `${templatePath}/jobs?tracer=:tracer_id`, summary: "List render jobs for this composition, optionally scoped to a tracer." },
3453
- { method: "GET", path: `${templatePath}/jobs/:job_id`, summary: "Read one render job record for this composition." },
3454
- { method: "GET", path: `${templatePath}/jobs/:job_id/artifacts`, summary: "Read artifacts for one composition render job." },
3455
- { method: "GET", path: `${templatePath}/jobs/:job_id/logs`, summary: "Read timestamped job logs for one run." },
3456
- { method: "POST", path: `${templatePath}/jobs/:job_id/cancel`, summary: "Cancel a queued or running job." },
3457
- { method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id`, summary: "Read one render job record by id across all compositions for the current customer." },
3458
- { method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id/logs`, summary: "Read timestamped job logs by job id without knowing the composition." },
3459
- { method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id/artifacts`, summary: "Read artifacts by job id without knowing the composition." },
3460
- { method: "GET", path: `${USER_PREFIX}/me/jobs/:job_id/debug`, summary: "Read a consolidated job debug bundle with status, error, artifacts, structured logs, and API history." },
3461
- { method: "GET", path: `${DIRECTOR_PREFIX}/jobs/:job_id/debug`, summary: "Director-only consolidated job debug bundle using x-superagency-key." },
3462
- { method: "GET", path: `/templates/${template.id}/editor/docs`, summary: "Open the HyperFrames composition workspace." },
3463
- { method: "GET", path: `/templates/${template.id}/editor/tracer/:tracer_id`, summary: "Open the tracer-grouped render view for this composition." },
3464
- { method: "GET", path: `/templates/${template.id}/editor/tracer/:tracer_id/history`, summary: "Load tracer-scoped API history for the editor tracer page." }
3465
- ];
3466
- }
3467
3442
  function buildPrimitiveEditorDocsRoutes() {
3468
3443
  const operationRoutes = primitiveRegistry.list().flatMap((primitive) => Object.entries(primitive.operations).map(([name, operation]) => ({
3469
3444
  method: "POST",
@@ -3481,7 +3456,7 @@ function buildPrimitiveEditorDocsRoutes() {
3481
3456
  { method: "POST", path: `${PRIMITIVES_PREFIX}/videos/generate`, summary: "Primitive AI video generation job using saved OpenAI, Gemini, or OpenRouter provider keys. Body must be { tracer, payload: { prompt, provider?, model?, input_references?, frame_images?, aspect_ratio?, duration?, resolution?, generate_audio? }, webhook_url? }. input_references must be direct image asset URLs, not webpage or social post URLs. duration is seconds, not milliseconds; use duration: 4 for a 4-second AI video. Defaults: openai/sora-2, gemini/veo-3.0-generate-001, openrouter/bytedance/seedance-2.0. Use this for new AI-generated footage." },
3482
3457
  { method: "POST", path: `${PRIMITIVES_PREFIX}/videos/normalize`, summary: "Primitive video normalization job. Body must be { tracer, payload: { source_video_url, max_width?, max_height?, crf?, preset?, max_video_bitrate_kbps?, audio_bitrate_kbps? }, webhook_url? }." },
3483
3458
  { method: "POST", path: `${PRIMITIVES_PREFIX}/videos/render-slides`, summary: "Primitive media slideshow render job. Body must be { tracer, payload: { slides: [{ media_url, duration_ms, media_type?, fit? }], width?, height?, background_color?, default_fit? }, webhook_url? }." },
3484
- { method: "POST", path: `${PRIMITIVES_PREFIX}/hyperframes/render`, summary: "Primitive HyperFrames timeline/layer render job. Body must be { tracer, payload: { composition_html | composition: { composition_id, width?, height?, fps?, layers: [{ id, kind, start, duration, src?, text?, html?, x?, y?, width?, height?, track?, z? }] }, chunk_size?, max_parallel_chunks?, output_key? }, webhook_url? }. Use this instead of creating new per-template REST APIs." },
3459
+ { method: "POST", path: `${PRIMITIVES_PREFIX}/timeline/render`, summary: "Primitive timeline/layer render job. Body must be { tracer, payload: { composition_html | composition: { composition_id, width?, height?, fps?, layers: [{ id, kind, start, duration, src?, text?, html?, x?, y?, width?, height?, track?, z? }] }, chunk_size?, max_parallel_chunks?, output_key? }, webhook_url? }. Use this instead of creating new per-template REST APIs." },
3485
3460
  { method: "POST", path: `${PRIMITIVES_PREFIX}/videos/trim`, summary: "Primitive video trim job. Body must be { tracer, payload: { source_video_url, start_ms, duration_ms|end_ms, max_width?, max_height?, crf?, preset?, audio_bitrate_kbps? }, webhook_url? }." },
3486
3461
  { method: "POST", path: `${PRIMITIVES_PREFIX}/videos/extract-audio`, summary: "Primitive audio extraction job from a source video. Body must be { tracer, payload: { source_video_url, output_format?, audio_bitrate_kbps? }, webhook_url? }." },
3487
3462
  { method: "POST", path: `${PRIMITIVES_PREFIX}/videos/probe`, summary: "Primitive video probe job. Body must be { tracer, payload: { source_video_url }, webhook_url? }. Returns durable JSON metadata." },
@@ -3509,59 +3484,6 @@ function buildPrimitiveEditorDocsRoutes() {
3509
3484
  { method: "POST", path: `${PRIMITIVES_PREFIX}/:primitive_kind/jobs/:job_id/cancel`, summary: "Cancel a queued or running primitive-kind job." }
3510
3485
  ];
3511
3486
  }
3512
- function buildTemplateEditorChatContext(input) {
3513
- return {
3514
- page: input.page,
3515
- tracerId: input.tracerId ?? null,
3516
- tracers: Array.isArray(input.tracers) ? input.tracers.filter((value) => value.trim().length > 0) : [],
3517
- defaultRequestTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
3518
- activeTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
3519
- templateId: input.template.id,
3520
- templateSlug: input.template.slugId,
3521
- templateTitle: input.template.about.title,
3522
- templateDescription: input.template.about.description,
3523
- docsRoutes: input.docsRoutes
3524
- };
3525
- }
3526
- async function buildTemplateEditorChatBoot(input) {
3527
- const directorSkill = readRootSkillFile("SKILL.director.md", "SKILL.user.md") || null;
3528
- const templateSkill = input.template.skillPath
3529
- ? (() => {
3530
- try {
3531
- return readFileSync(input.template.skillPath, "utf8");
3532
- }
3533
- catch {
3534
- return null;
3535
- }
3536
- })()
3537
- : null;
3538
- const availableProviders = input.customer
3539
- ? (await normalizeEditorChatProviderRows(input.customer.id)).map((entry) => entry.provider)
3540
- : [];
3541
- const currentTemplateConfig = input.customer
3542
- ? await getTemplateConfigForRuntime(input.customer.id, input.template.id)
3543
- : null;
3544
- return {
3545
- apiUrl: resolveEditorChatApiUrl(),
3546
- threadsUrl: `${API_PREFIX}/editor-chat/threads`,
3547
- vidfarmApiKey: input.customer ? await getVisibleApiKey(input.customer.id) : null,
3548
- cachedContext: {
3549
- directorSkill,
3550
- templateSkill,
3551
- availableProviders,
3552
- currentTemplateConfig
3553
- },
3554
- template: buildTemplateEditorChatContext({
3555
- page: input.page,
3556
- tracerId: input.tracerId ?? null,
3557
- tracers: input.tracers ?? [],
3558
- defaultRequestTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
3559
- activeTracer: input.defaultRequestTracer ?? input.activeTracer ?? input.tracerId ?? null,
3560
- template: input.template,
3561
- docsRoutes: input.docsRoutes
3562
- })
3563
- };
3564
- }
3565
3487
  async function buildLibraryEditorChatBoot(input) {
3566
3488
  const directorSkill = readRootSkillFile("SKILL.director.md", "SKILL.user.md") || null;
3567
3489
  const availableProviders = (await normalizeEditorChatProviderRows(input.customer.id)).map((entry) => entry.provider);
@@ -4197,7 +4119,7 @@ function createEditorFrontendActionTool(input) {
4197
4119
  }
4198
4120
  function createEditorActionTool() {
4199
4121
  return tool({
4200
- description: "Mutate the HyperFrames composition the user is editing. The browser editor applies the action directly to composition.html and the timeline updates immediately. Use action_type=set_layer_timing to change a layer's start/duration/track/playback_start (all in seconds, except track which is an integer index). Use set_layer_visual to change a layer's x/y/width/height as percentages (0-100) relative to the composition viewport, or font_size in pixels. Use set_layer_text to replace the inner text content of a text or caption layer. Use replace_composition_html only when a partial action cannot express the change — pass the full new HTML document. Always pass layer_key copied verbatim from the editor_context attached to the user's message; never invent layer keys. Include a one-line explanation describing the change so the user can audit it.",
4122
+ description: "Mutate the timeline composition the user is editing. The browser editor applies the action directly to composition.html and the timeline updates immediately. Use action_type=set_layer_timing to change a layer's start/duration/track/playback_start (all in seconds, except track which is an integer index). Use set_layer_visual to change a layer's x/y/width/height as percentages (0-100) relative to the composition viewport, or font_size in pixels. Use set_layer_text to replace the inner text content of a text or caption layer. Use replace_composition_html only when a partial action cannot express the change — pass the full new HTML document. Always pass layer_key copied verbatim from the editor_context attached to the user's message; never invent layer keys. Include a one-line explanation describing the change so the user can audit it.",
4201
4123
  inputSchema: z.object({
4202
4124
  action_type: z.enum(["set_layer_timing", "set_layer_visual", "set_layer_text", "replace_composition_html"]),
4203
4125
  layer_key: z.string().min(1).optional().describe("Stable layer identifier. Required for set_layer_timing, set_layer_visual, and set_layer_text. Read it from the editor_context layers list."),
@@ -4505,12 +4427,6 @@ async function streamEditorChatAgent(input, send) {
4505
4427
  httpExchanges
4506
4428
  };
4507
4429
  }
4508
- function matchesTemplateApiHistoryPath(pathname, template) {
4509
- return pathname.includes(`${TEMPLATES_PREFIX}/${template.id}`) || pathname.includes(`${TEMPLATES_PREFIX}/${template.slugId}`);
4510
- }
4511
- function matchesEditorApiHistoryPath(pathname, template) {
4512
- return matchesTemplateApiHistoryPath(pathname, template) || pathname.startsWith(PRIMITIVES_PREFIX);
4513
- }
4514
4430
  function formatTimelineSeconds(value) {
4515
4431
  if (!Number.isFinite(value)) {
4516
4432
  return "unknown";
@@ -4685,7 +4601,7 @@ function renderHyperframesCompositionWorkspacePage(input) {
4685
4601
  const sourceAssets = getDraftSourceAssets(compositionData);
4686
4602
  const editorLayers = normalizeHyperframesDraftLayers(input.composition.draftPath, compositionData);
4687
4603
  const durationSeconds = Math.max(1, estimateDraftDurationSeconds(compositionData, editorLayers, sourceAssets) ?? input.composition.durationSeconds ?? 10);
4688
- const compositionUrl = `/hyperframes/${encodeURIComponent(input.composition.slugId)}/composition`;
4604
+ const compositionUrl = `/editor/${encodeURIComponent(input.composition.slugId)}/composition`;
4689
4605
  const editorBoot = {
4690
4606
  composition: {
4691
4607
  title: input.composition.title,
@@ -4702,7 +4618,7 @@ function renderHyperframesCompositionWorkspacePage(input) {
4702
4618
  }))
4703
4619
  };
4704
4620
  return renderPageShell({
4705
- title: `${input.composition.title} | HyperFrames`,
4621
+ title: `${input.composition.title}`,
4706
4622
  mainClass: "is-wide",
4707
4623
  body: `
4708
4624
  <section class="frame hf-editor-frame">
@@ -4723,7 +4639,7 @@ function renderHyperframesCompositionWorkspacePage(input) {
4723
4639
  <div class="frame-body hf-editor-body">
4724
4640
  <section class="hf-editor-command">
4725
4641
  <div>
4726
- <div class="eyebrow">Draft HyperFrames Template</div>
4642
+ <div class="eyebrow">Draft Template</div>
4727
4643
  <h1>${escapeHtml(input.composition.title)}</h1>
4728
4644
  <p>${escapeHtml(input.composition.viralDna)}</p>
4729
4645
  </div>
@@ -5244,17 +5160,17 @@ async function renderHyperframesStudioEditorPage(input) {
5244
5160
  <head>
5245
5161
  <meta charset="UTF-8" />
5246
5162
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5247
- <title>${escapeHtml(input.composition.title)} | HyperFrames Studio</title>
5163
+ <title>${escapeHtml(input.composition.title)} | Studio</title>
5248
5164
  <link rel="preconnect" href="https://fonts.googleapis.com" />
5249
5165
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
5250
5166
  <link rel="icon" href="/assets/favicon.ico" />
5251
- <link rel="stylesheet" href="/assets/hyperframes-editor/app.css" />
5167
+ <link rel="stylesheet" href="/assets/editor/app.css" />
5252
5168
  <style>html,body{margin:0;background:#050604;color:#fffbe6;}#root{min-height:100vh;}</style>
5253
5169
  </head>
5254
5170
  <body>
5255
5171
  <div id="root"></div>
5256
5172
  <script id="hf-boot" type="application/json">${escapeJsonForHtml(boot)}</script>
5257
- <script type="module" src="/assets/hyperframes-editor/app.js"></script>
5173
+ <script type="module" src="/assets/editor/app.js"></script>
5258
5174
  </body>
5259
5175
  </html>`;
5260
5176
  }
@@ -5294,7 +5210,7 @@ app.get("/", async (c) => {
5294
5210
  return redirect(c, customer ? withAccountQuery("/library", customer.id) : "/discover");
5295
5211
  });
5296
5212
  app.get("/discover", async (c) => renderApprovedHomepage(c));
5297
- app.get("/hyperframes/:compositionId/editor", async (c) => {
5213
+ app.get("/editor/:compositionId", async (c) => {
5298
5214
  const compositionId = c.req.param("compositionId");
5299
5215
  const customer = await getOptionalPreferredBrowserCustomer(c);
5300
5216
  const tiktokMatch = /^tiktok-([A-Za-z0-9]+)$/.exec(compositionId);
@@ -5305,7 +5221,7 @@ app.get("/hyperframes/:compositionId/editor", async (c) => {
5305
5221
  if (video) {
5306
5222
  const forkParam = c.req.query("fork");
5307
5223
  if (video.defaultForkId && !forkParam) {
5308
- const path = `/hyperframes/${encodeURIComponent(compositionId)}/editor?fork=${encodeURIComponent(video.defaultForkId)}`;
5224
+ const path = `/editor/${encodeURIComponent(compositionId)}?fork=${encodeURIComponent(video.defaultForkId)}`;
5309
5225
  return redirect(c, path);
5310
5226
  }
5311
5227
  if (!customer) {
@@ -5345,7 +5261,7 @@ app.get("/hyperframes/:compositionId/editor", async (c) => {
5345
5261
  }
5346
5262
  return c.html(await renderHyperframesStudioEditorPage({ composition, customer }));
5347
5263
  });
5348
- app.post("/hyperframes/:compositionId/publish", async (c) => {
5264
+ app.post("/editor/:compositionId/export", async (c) => {
5349
5265
  const composition = getHyperframesDiscoverTemplate(c.req.param("compositionId"));
5350
5266
  if (!composition) {
5351
5267
  return c.json({ error: "Composition not found" }, 404);
@@ -5365,7 +5281,7 @@ app.post("/hyperframes/:compositionId/publish", async (c) => {
5365
5281
  const primitive = primitiveRegistry.get("primitive:hyperframes_render");
5366
5282
  const operation = primitive?.operations.run;
5367
5283
  if (!primitive || !operation) {
5368
- return c.json({ error: "HyperFrames render primitive not found" }, 500);
5284
+ return c.json({ error: "Timeline render primitive not found" }, 500);
5369
5285
  }
5370
5286
  const payload = operation.inputSchema.parse({
5371
5287
  composition_html: renderHyperframesDraftCompositionHtml(composition),
@@ -5423,7 +5339,7 @@ app.post("/hyperframes/:compositionId/publish", async (c) => {
5423
5339
  throw error;
5424
5340
  }
5425
5341
  });
5426
- app.get("/hyperframes/:compositionId/composition", async (c) => {
5342
+ app.get("/editor/:compositionId/composition", async (c) => {
5427
5343
  const compositionId = c.req.param("compositionId");
5428
5344
  const tiktokMatch = /^tiktok-([A-Za-z0-9]+)$/.exec(compositionId);
5429
5345
  if (tiktokMatch) {
@@ -5444,7 +5360,7 @@ app.get("/hyperframes/:compositionId/composition", async (c) => {
5444
5360
  }
5445
5361
  return c.html(renderHyperframesDraftCompositionHtml(composition));
5446
5362
  });
5447
- app.get("/hyperframes/:compositionId/composition.json", async (c) => {
5363
+ app.get("/editor/:compositionId/composition.json", async (c) => {
5448
5364
  const composition = getHyperframesDiscoverTemplate(c.req.param("compositionId"));
5449
5365
  if (!composition) {
5450
5366
  return c.json({ error: "Composition not found" }, 404);
@@ -5455,7 +5371,7 @@ app.get("/hyperframes/:compositionId/composition.json", async (c) => {
5455
5371
  }
5456
5372
  return c.json(compositionData);
5457
5373
  });
5458
- app.get("/hyperframes/:compositionId/skill", async (c) => {
5374
+ app.get("/editor/:compositionId/skill", async (c) => {
5459
5375
  const composition = getHyperframesDiscoverTemplate(c.req.param("compositionId"));
5460
5376
  if (!composition) {
5461
5377
  return c.text("Composition not found", 404);
@@ -5464,7 +5380,7 @@ app.get("/hyperframes/:compositionId/skill", async (c) => {
5464
5380
  "content-type": "text/markdown; charset=utf-8"
5465
5381
  });
5466
5382
  });
5467
- app.get("/hyperframes/:compositionId/editor/docs/history", async (c) => {
5383
+ app.get("/editor/:compositionId/docs/history", async (c) => {
5468
5384
  const customer = await requireBrowserCustomer(c);
5469
5385
  if (!customer) {
5470
5386
  return c.json({ error: "Unauthorized" }, 401);
@@ -5502,7 +5418,7 @@ app.get("/hyperframes/:compositionId/editor/docs/history", async (c) => {
5502
5418
  const linkedTemplate = String(entry.linkedJob?.template_id ?? "").toLowerCase();
5503
5419
  const pathname = String(entry.path || "").toLowerCase();
5504
5420
  return linkedTemplate === "primitive:hyperframes_render"
5505
- || pathname.includes(`${PRIMITIVES_PREFIX}/hyperframes/render`);
5421
+ || pathname.includes(`${PRIMITIVES_PREFIX}/timeline/render`);
5506
5422
  });
5507
5423
  const filteredEntries = parseCreatedAtIdCursor(query.cursor)
5508
5424
  ? entries.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(parseCreatedAtIdCursor(query.cursor).createdAt)
@@ -5517,7 +5433,7 @@ app.get("/hyperframes/:compositionId/editor/docs/history", async (c) => {
5517
5433
  next_cursor: page.nextCursor
5518
5434
  });
5519
5435
  });
5520
- app.patch("/hyperframes/:compositionId/editor/tracers", async (c) => {
5436
+ app.patch("/editor/:compositionId/tracers", async (c) => {
5521
5437
  const customer = await requireBrowserCustomer(c);
5522
5438
  if (!customer) {
5523
5439
  return c.json({ error: "Unauthorized" }, 401);
@@ -5551,7 +5467,6 @@ app.get(`${ACCOUNT_FRONTEND_PREFIX}`, (c) => redirect(c, withAccountQuery("/libr
5551
5467
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/discover`, (c) => redirectToPublicPath(c, "/discover"));
5552
5468
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/help`, (c) => redirectToPublicPath(c, "/help"));
5553
5469
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/library`, (c) => dispatchScopedGetToLegacyRoute(c, "/library"));
5554
- app.get(`${ACCOUNT_FRONTEND_PREFIX}/chat`, (c) => dispatchScopedGetToLegacyRoute(c, "/chat"));
5555
5470
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/inpaint`, (c) => dispatchScopedGetToLegacyRoute(c, "/inpaint"));
5556
5471
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/calendar`, (c) => dispatchScopedGetToLegacyRoute(c, "/calendar"));
5557
5472
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/settings`, (c) => dispatchScopedGetToLegacyRoute(c, "/settings"));
@@ -5560,39 +5475,6 @@ app.get(`${ACCOUNT_FRONTEND_PREFIX}/job-runs/history`, (c) => dispatchScopedGetT
5560
5475
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/calendar/feed`, (c) => dispatchScopedGetToLegacyRoute(c, "/calendar/feed"));
5561
5476
  app.get(`${ACCOUNT_FRONTEND_PREFIX}/settings/flockposter`, (c) => dispatchScopedGetToLegacyRoute(c, "/settings/flockposter"));
5562
5477
  app.post(`${ACCOUNT_FRONTEND_PREFIX}/settings/wallet/funding-link`, (c) => dispatchScopedPostToLegacyRoute(c, "/settings/wallet/funding-link"));
5563
- app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/docs`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/docs`));
5564
- app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/docs/history`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/docs/history`));
5565
- app.patch(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/tracers`, async (c) => {
5566
- const customer = await requireBrowserCustomer(c);
5567
- if (!customer) {
5568
- return c.json({ error: "Unauthorized" }, 401);
5569
- }
5570
- const template = templateRegistry.get(c.req.param("templateId"));
5571
- if (!template) {
5572
- return c.json({ error: "Template not found" }, 404);
5573
- }
5574
- const body = tracerRenameSchema.parse(await c.req.json());
5575
- const jobsUpdated = jobs.renameTracer({
5576
- customerId: customer.id,
5577
- templateId: template.id,
5578
- fromTracer: body.from_tracer,
5579
- toTracer: body.to_tracer
5580
- });
5581
- const historyResult = await apiCallHistoryStore.renameTracer({
5582
- customerId: customer.id,
5583
- fromTracer: body.from_tracer,
5584
- toTracer: body.to_tracer
5585
- });
5586
- return c.json({
5587
- ok: true,
5588
- from_tracer: body.from_tracer,
5589
- to_tracer: body.to_tracer,
5590
- jobs_updated: jobsUpdated,
5591
- history_entries_updated: historyResult.updated
5592
- });
5593
- });
5594
- app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/tracer/:tracerId`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/tracer/${encodeURIComponent(c.req.param("tracerId"))}`));
5595
- app.get(`${ACCOUNT_FRONTEND_PREFIX}/templates/:templateId/editor/tracer/:tracerId/history`, (c) => dispatchScopedGetToLegacyRoute(c, `/templates/${encodeURIComponent(c.req.param("templateId"))}/editor/tracer/${encodeURIComponent(c.req.param("tracerId"))}/history`));
5596
5478
  app.get("/discover/feed", async (c) => {
5597
5479
  const limit = clampPageLimit(Number(c.req.query("limit") || ""), 100, 200);
5598
5480
  const page = await getApprovedHomepageTemplates(c, {
@@ -5895,9 +5777,9 @@ function serveHyperframesEditorAsset(filePath) {
5895
5777
  }
5896
5778
  });
5897
5779
  }
5898
- app.get("/assets/hyperframes-editor/*", (c) => {
5780
+ app.get("/assets/editor/*", (c) => {
5899
5781
  const pathname = decodeURIComponent(new URL(c.req.url).pathname);
5900
- const relative = pathname.replace(/^\/assets\/hyperframes-editor\//, "");
5782
+ const relative = pathname.replace(/^\/assets\/editor\//, "");
5901
5783
  const filePath = resolveHyperframesEditorAsset(relative);
5902
5784
  if (!filePath)
5903
5785
  return c.text("Not found", 404);
@@ -5981,7 +5863,7 @@ async function publishCompositionToRenderer(input) {
5981
5863
  const primitive = primitiveRegistry.get("primitive:hyperframes_render");
5982
5864
  const operation = primitive?.operations.run;
5983
5865
  if (!primitive || !operation) {
5984
- throw new Error("HyperFrames render primitive not found");
5866
+ throw new Error("Timeline render primitive not found");
5985
5867
  }
5986
5868
  const tracerValue = tracer ?? `hf-publish-${slugId}-${Date.now()}`;
5987
5869
  const payload = operation.inputSchema.parse({
@@ -6138,7 +6020,7 @@ async function snapshotForkVersion(input) {
6138
6020
  if (workingJson) {
6139
6021
  await storage.putBuffer(storage.compositionForkVersionKey(input.fork.id, version, "composition.json"), Buffer.from(workingJson, "utf8"), "application/json");
6140
6022
  }
6141
- await serverlessRecords.createForkVersion({
6023
+ const versionRecord = await serverlessRecords.createForkVersion({
6142
6024
  forkId: input.fork.id,
6143
6025
  ownerCustomerId: input.fork.customerId,
6144
6026
  version,
@@ -6148,10 +6030,19 @@ async function snapshotForkVersion(input) {
6148
6030
  storagePrefix: storage.compositionForkVersionPrefix(input.fork.id, version),
6149
6031
  renderJobId: input.renderJobId ?? null
6150
6032
  });
6151
- await serverlessRecords.updateCompositionFork({
6033
+ const updatedFork = await serverlessRecords.updateCompositionFork({
6152
6034
  forkId: input.fork.id,
6153
6035
  patch: { latestVersion: version }
6154
6036
  });
6037
+ const forkForManifest = updatedFork ?? { ...input.fork, latestVersion: version };
6038
+ // Write the frozen version manifest, then refresh the working manifest so
6039
+ // its latest_version pointer stays in sync.
6040
+ await writeForkManifest(storage, forkForManifest, {
6041
+ kind: "version",
6042
+ version,
6043
+ createdAt: versionRecord.createdAt
6044
+ });
6045
+ await writeForkManifest(storage, forkForManifest, { kind: "working" });
6155
6046
  return version;
6156
6047
  }
6157
6048
  function serializeFork(fork, caps) {
@@ -6227,6 +6118,7 @@ app.post(COMPOSITIONS_PREFIX, async (c) => {
6227
6118
  title
6228
6119
  });
6229
6120
  await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), rawHtml, "text/html; charset=utf-8");
6121
+ await writeForkManifest(storage, fork, { kind: "working" });
6230
6122
  await serverlessRecords.updateSubmittedVideo({
6231
6123
  videoId: video.id,
6232
6124
  patch: { defaultForkId: fork.id }
@@ -6252,6 +6144,7 @@ app.post(COMPOSITIONS_PREFIX, async (c) => {
6252
6144
  });
6253
6145
  await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), compositionHtml, "text/html; charset=utf-8");
6254
6146
  await storage.putBuffer(storage.compositionForkWorkingKey(fork.id, "composition.json"), Buffer.from(compositionJsonText, "utf8"), "application/json");
6147
+ await writeForkManifest(storage, fork, { kind: "working" });
6255
6148
  return c.json(serializeFork(fork, {
6256
6149
  isOwner: true, canView: true, canEdit: true, canPublish: true, canShare: true, canDelete: true, role: "owner"
6257
6150
  }), 201);
@@ -6301,6 +6194,24 @@ app.get(`${COMPOSITIONS_PREFIX}/:forkId/composition.html`, async (c) => {
6301
6194
  return forkAccessErrorResponse(c, error);
6302
6195
  }
6303
6196
  });
6197
+ app.get(`${COMPOSITIONS_PREFIX}/:forkId/manifest.json`, async (c) => {
6198
+ const customer = await getBrowserCustomer(c);
6199
+ try {
6200
+ const access = await assertForkAccess({ forkId: c.req.param("forkId"), customerId: customer?.id ?? null, shareToken: readShareToken(c) }, "view");
6201
+ const text = await storage.readText(storage.compositionForkWorkingKey(access.fork.id, "manifest.json"));
6202
+ if (!text)
6203
+ return c.json({ ok: false, error: "Manifest missing" }, 404);
6204
+ try {
6205
+ return c.json(JSON.parse(text));
6206
+ }
6207
+ catch {
6208
+ return c.json({ ok: false, error: "Manifest is not valid JSON" }, 500);
6209
+ }
6210
+ }
6211
+ catch (error) {
6212
+ return forkAccessErrorResponse(c, error);
6213
+ }
6214
+ });
6304
6215
  app.put(`${COMPOSITIONS_PREFIX}/:forkId/composition.html`, async (c) => {
6305
6216
  const customer = await getBrowserCustomer(c);
6306
6217
  if (!customer)
@@ -6312,7 +6223,8 @@ app.put(`${COMPOSITIONS_PREFIX}/:forkId/composition.html`, async (c) => {
6312
6223
  return c.json({ ok: false, error: "Composition HTML is missing data-composition-id" }, 400);
6313
6224
  }
6314
6225
  await storage.putText(storage.compositionForkWorkingKey(access.fork.id, "composition.html"), html, "text/html; charset=utf-8");
6315
- await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
6226
+ const updated = await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
6227
+ await writeForkManifest(storage, updated ?? access.fork, { kind: "working" });
6316
6228
  return c.json({ ok: true, bytes: html.length });
6317
6229
  }
6318
6230
  catch (error) {
@@ -6350,7 +6262,8 @@ app.patch(`${COMPOSITIONS_PREFIX}/:forkId/composition.json`, async (c) => {
6350
6262
  const existing = existingText ? asRecord(JSON.parse(existingText)) ?? {} : {};
6351
6263
  const merged = { ...existing, ...patch };
6352
6264
  await storage.putBuffer(storage.compositionForkWorkingKey(access.fork.id, "composition.json"), Buffer.from(JSON.stringify(merged, null, 2), "utf8"), "application/json");
6353
- await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
6265
+ const updated = await serverlessRecords.updateCompositionFork({ forkId: access.fork.id, patch: {} });
6266
+ await writeForkManifest(storage, updated ?? access.fork, { kind: "working" });
6354
6267
  return c.json(merged);
6355
6268
  }
6356
6269
  catch (error) {
@@ -6728,7 +6641,7 @@ app.post(`${COMPOSITIONS_PREFIX}/:forkId/ghostcut-poll`, async (c) => {
6728
6641
  return c.json({ ok: false, error: error instanceof Error ? error.message : "ghostcut-poll failed" }, 500);
6729
6642
  }
6730
6643
  });
6731
- app.post(`${COMPOSITIONS_PREFIX}/:forkId/publish`, async (c) => {
6644
+ app.post(`${COMPOSITIONS_PREFIX}/:forkId/export`, async (c) => {
6732
6645
  const caller = await getBrowserCustomer(c);
6733
6646
  if (!caller)
6734
6647
  return c.json({ ok: false, error: "Unauthorized" }, 401);
@@ -6746,6 +6659,7 @@ app.post(`${COMPOSITIONS_PREFIX}/:forkId/publish`, async (c) => {
6746
6659
  return c.json({ ok: false, error: "Composition HTML is missing data-composition-id" }, 400);
6747
6660
  }
6748
6661
  await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), submittedHtml, "text/html; charset=utf-8");
6662
+ await writeForkManifest(storage, fork, { kind: "working" });
6749
6663
  }
6750
6664
  const versionParam = readNonEmptyString(requestBody.version);
6751
6665
  const requestedVersion = versionParam ? Number.parseInt(versionParam, 10) : null;
@@ -7042,6 +6956,9 @@ app.patch(`${COMPOSITIONS_PREFIX}/:forkId/visibility`, async (c) => {
7042
6956
  forkId: access.fork.id,
7043
6957
  patch: { visibility }
7044
6958
  });
6959
+ if (updated) {
6960
+ await writeForkManifest(storage, updated, { kind: "working" });
6961
+ }
7045
6962
  return c.json(serializeFork(updated, access.capabilities));
7046
6963
  }
7047
6964
  catch (error) {
@@ -7150,6 +7067,27 @@ app.get(`${COMPOSITIONS_PREFIX}/:forkId/versions/:version/composition.json`, asy
7150
7067
  return forkAccessErrorResponse(c, error);
7151
7068
  }
7152
7069
  });
7070
+ app.get(`${COMPOSITIONS_PREFIX}/:forkId/versions/:version/manifest.json`, async (c) => {
7071
+ const customer = await getBrowserCustomer(c);
7072
+ try {
7073
+ const access = await assertForkAccess({ forkId: c.req.param("forkId"), customerId: customer?.id ?? null, shareToken: readShareToken(c) }, "view");
7074
+ const version = Number.parseInt(c.req.param("version"), 10);
7075
+ if (!Number.isFinite(version))
7076
+ return c.json({ ok: false, error: "Invalid version" }, 400);
7077
+ const text = await storage.readText(storage.compositionForkVersionKey(access.fork.id, version, "manifest.json"));
7078
+ if (!text)
7079
+ return c.json({ ok: false, error: "Version manifest not found" }, 404);
7080
+ try {
7081
+ return c.json(JSON.parse(text));
7082
+ }
7083
+ catch {
7084
+ return c.json({ ok: false, error: "Manifest is not valid JSON" }, 500);
7085
+ }
7086
+ }
7087
+ catch (error) {
7088
+ return forkAccessErrorResponse(c, error);
7089
+ }
7090
+ });
7153
7091
  app.post(`${COMPOSITIONS_PREFIX}/:forkId/versions`, async (c) => {
7154
7092
  const customer = await getBrowserCustomer(c);
7155
7093
  if (!customer)
@@ -7398,6 +7336,7 @@ app.post(`${VIDEOS_PREFIX}/:videoId/decompose`, async (c) => {
7398
7336
  });
7399
7337
  await storage.putText(storage.compositionForkWorkingKey(fork.id, "composition.html"), readFileSync(compositionFile, "utf8"), "text/html; charset=utf-8");
7400
7338
  await storage.putBuffer(storage.compositionForkWorkingKey(fork.id, "composition.json"), Buffer.from(readFileSync(compositionJsonFile, "utf8"), "utf8"), "application/json");
7339
+ await writeForkManifest(storage, fork, { kind: "working" });
7401
7340
  return c.json(serializeFork(fork, {
7402
7341
  isOwner: true, canView: true, canEdit: true, canPublish: true, canShare: true, canDelete: true, role: "owner"
7403
7342
  }), 201);
@@ -8110,16 +8049,13 @@ app.get("/library", async (c) => {
8110
8049
  }))
8111
8050
  ];
8112
8051
  const posts = await Promise.all((await serverlessRecords.listReadyPosts(customer.id))
8052
+ .filter((post) => (post.source ?? "legacy") === "hyperframes")
8113
8053
  .filter((post) => post.status === "ready" || post.status === "scheduled")
8114
8054
  .map(async (post) => {
8115
8055
  const serialized = serializeReadyPost(c, post, { includePrivate: true });
8116
- const linkedJob = post.tracer
8117
- ? (await jobs.listJobsAsync({ customerId: post.customerId, tracer: post.tracer, limit: 100 }))
8118
- .find((job) => !job.templateId.startsWith("primitive:") && Boolean(templateRegistry.get(job.templateId)))
8119
- : null;
8120
8056
  return {
8121
8057
  ...serialized,
8122
- template_id: linkedJob?.templateId ?? null
8058
+ template_id: post.compositionId ?? null
8123
8059
  };
8124
8060
  }));
8125
8061
  return c.html(renderLibraryPage({
@@ -8138,21 +8074,6 @@ app.get("/library", async (c) => {
8138
8074
  })
8139
8075
  }));
8140
8076
  });
8141
- app.get("/chat", async (c) => {
8142
- const customer = await getPreferredBrowserCustomer(c);
8143
- if (!customer) {
8144
- return redirect(c, "/login");
8145
- }
8146
- return c.html(renderChatPage({
8147
- userId: customer.id,
8148
- currentAccountId: customer.id,
8149
- name: customer.name,
8150
- email: customer.email,
8151
- editorChat: await buildChatBrainstormEditorChatBoot({
8152
- customer
8153
- })
8154
- }));
8155
- });
8156
8077
  app.get("/inpaint", async (c) => {
8157
8078
  const customer = await requireBrowserCustomer(c);
8158
8079
  if (!customer) {
@@ -8233,239 +8154,6 @@ app.get("/job-runs/history", async (c) => {
8233
8154
  next_cursor: page.nextCursor
8234
8155
  });
8235
8156
  });
8236
- app.get("/templates/:templateId/editor/docs", async (c) => {
8237
- const template = templateRegistry.get(c.req.param("templateId"));
8238
- if (!template) {
8239
- return c.text("Template not found", 404);
8240
- }
8241
- const customer = await getPreferredBrowserCustomer(c);
8242
- const url = new URL(c.req.url);
8243
- const tracers = parseTracerList(url.searchParams);
8244
- const docsRoutes = [
8245
- ...buildTemplateEditorDocsRoutes(template),
8246
- ...buildPrimitiveEditorDocsRoutes()
8247
- ];
8248
- const directorSkill = readRootSkillFile("SKILL.director.md", "SKILL.user.md") || null;
8249
- return c.html(renderTemplateEditorDocsPage({
8250
- account: {
8251
- userId: customer?.id ?? null,
8252
- name: customer?.name ?? null,
8253
- email: customer?.email ?? null,
8254
- isLoggedIn: Boolean(customer)
8255
- },
8256
- template: {
8257
- id: template.id,
8258
- slugId: template.slugId,
8259
- version: template.version,
8260
- title: template.about.title,
8261
- description: template.about.description,
8262
- difficulty: template.about.difficulty,
8263
- viralDna: template.about.viral_dna,
8264
- visualDna: template.about.visual_dna,
8265
- sound: template.about.sound,
8266
- proposalGenerator: template.about.proposal_generator,
8267
- skeletonPrompt: template.about.skeleton_prompt,
8268
- samplePrompts: template.about.sample_prompts,
8269
- examples: template.examples.map((example) => example.map((entry) => ({
8270
- mediaUrl: resolveTemplateExampleMediaUrl(c, template.id, entry.media_url),
8271
- note: entry.note
8272
- }))),
8273
- previewUrl: template.about.preview_media[0] ? resolveTemplateAboutMediaUrl(c, template.id, template.about.preview_media[0]) : null,
8274
- skillUrl: buildAbsoluteUrl(c, `${TEMPLATES_PREFIX}/${template.id}/skill`),
8275
- operations: Object.entries(template.operations).map(([name, operation]) => ({
8276
- name,
8277
- description: operation.description
8278
- }))
8279
- },
8280
- directorSkill,
8281
- docsRoutes,
8282
- sampleTracerId: tracers[0] ?? `${template.slugId}-001`,
8283
- historyEndpoint: withAccountQuery(`/templates/${template.id}/editor/docs/history?limit=250`, customer?.id),
8284
- tracerEndpoint: withAccountQuery(`/templates/${template.id}/editor/tracers`, customer?.id),
8285
- initialTracers: tracers,
8286
- editorChat: await buildTemplateEditorChatBoot({
8287
- customer,
8288
- template,
8289
- page: "docs",
8290
- tracers,
8291
- activeTracer: tracers[0] ?? null,
8292
- docsRoutes
8293
- })
8294
- }));
8295
- });
8296
- app.get("/templates/:templateId/editor/docs/history", async (c) => {
8297
- const customer = await requireBrowserCustomer(c);
8298
- if (!customer) {
8299
- return c.json({ error: "Unauthorized" }, 401);
8300
- }
8301
- const template = templateRegistry.get(c.req.param("templateId"));
8302
- if (!template) {
8303
- return c.json({ error: "Template not found" }, 404);
8304
- }
8305
- const url = new URL(c.req.url);
8306
- const query = apiCallHistoryQuerySchema.parse({
8307
- start_time: c.req.query("start_time"),
8308
- end_time: c.req.query("end_time"),
8309
- limit: c.req.query("limit"),
8310
- cursor: c.req.query("cursor"),
8311
- tracers: parseTracerList(url.searchParams)
8312
- });
8313
- const limit = clampPageLimit(query.limit, 250);
8314
- const tracerSet = new Set(query.tracers ?? []);
8315
- const selectedTracers = query.tracers ?? [];
8316
- const entries = (await buildJobRunHistoryEntries({
8317
- customerId: customer.id,
8318
- startTime: query.start_time,
8319
- endTime: query.end_time,
8320
- templateId: template.id,
8321
- tracers: selectedTracers,
8322
- includePrimitiveJobs: true,
8323
- entries: await apiCallHistoryStore.listEntries({
8324
- customerId: customer.id,
8325
- startTime: query.start_time,
8326
- endTime: query.end_time
8327
- })
8328
- })).filter((entry) => {
8329
- if (String(entry.method || "").toUpperCase() !== "POST") {
8330
- return false;
8331
- }
8332
- if (!matchesEditorApiHistoryPath(entry.path, template)) {
8333
- return false;
8334
- }
8335
- if (!tracerSet.size) {
8336
- return true;
8337
- }
8338
- return doesTracerMatchSelection(entry.tracer, tracerSet);
8339
- });
8340
- const filteredEntries = parseCreatedAtIdCursor(query.cursor)
8341
- ? entries.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(parseCreatedAtIdCursor(query.cursor).createdAt)
8342
- || (entry.createdAt === parseCreatedAtIdCursor(query.cursor).createdAt && entry.id < parseCreatedAtIdCursor(query.cursor).id)))
8343
- : entries;
8344
- const page = paginateDescendingByCreatedAt(filteredEntries, limit);
8345
- return c.json({
8346
- entries: page.items,
8347
- storage_driver: config.STORAGE_DRIVER,
8348
- template_id: template.id,
8349
- tracers: selectedTracers,
8350
- next_cursor: page.nextCursor
8351
- });
8352
- });
8353
- app.patch("/templates/:templateId/editor/tracers", async (c) => {
8354
- const customer = await requireBrowserCustomer(c);
8355
- if (!customer) {
8356
- return c.json({ error: "Unauthorized" }, 401);
8357
- }
8358
- const template = templateRegistry.get(c.req.param("templateId"));
8359
- if (!template) {
8360
- return c.json({ error: "Template not found" }, 404);
8361
- }
8362
- const body = tracerRenameSchema.parse(await c.req.json());
8363
- const jobsUpdated = jobs.renameTracer({
8364
- customerId: customer.id,
8365
- templateId: template.id,
8366
- fromTracer: body.from_tracer,
8367
- toTracer: body.to_tracer
8368
- });
8369
- const historyResult = await apiCallHistoryStore.renameTracer({
8370
- customerId: customer.id,
8371
- fromTracer: body.from_tracer,
8372
- toTracer: body.to_tracer
8373
- });
8374
- return c.json({
8375
- ok: true,
8376
- from_tracer: body.from_tracer,
8377
- to_tracer: body.to_tracer,
8378
- jobs_updated: jobsUpdated,
8379
- history_entries_updated: historyResult.updated
8380
- });
8381
- });
8382
- app.get("/templates/:templateId/editor/tracer/:tracerId", async (c) => {
8383
- const customer = await requireBrowserCustomer(c);
8384
- if (!customer) {
8385
- return redirect(c, "/login");
8386
- }
8387
- const template = templateRegistry.get(c.req.param("templateId"));
8388
- if (!template) {
8389
- return c.text("Template not found", 404);
8390
- }
8391
- const tracerId = c.req.param("tracerId");
8392
- const docsRoutes = [
8393
- ...buildTemplateEditorDocsRoutes(template),
8394
- ...buildPrimitiveEditorDocsRoutes()
8395
- ];
8396
- return c.html(renderJobRunsPage({
8397
- userId: customer.id,
8398
- currentAccountId: customer.id,
8399
- name: customer.name,
8400
- email: customer.email,
8401
- storageDriver: config.STORAGE_DRIVER,
8402
- jobs: [],
8403
- apiCallHistory: [],
8404
- loadEndpoint: withAccountQuery(`/templates/${template.id}/editor/tracer/${encodeURIComponent(tracerId)}/history?limit=250`, customer.id),
8405
- pageTitle: `${template.about.title} tracer`,
8406
- headerTitle: `${template.about.title} tracer`,
8407
- initialGroupBy: "route",
8408
- methodScope: "all",
8409
- editorChrome: {
8410
- backHref: withAccountQuery(`/templates/${template.id}/editor/docs`, customer.id),
8411
- templateTitle: template.about.title,
8412
- templateId: template.id,
8413
- templateSlug: template.slugId
8414
- },
8415
- editorChat: await buildTemplateEditorChatBoot({
8416
- customer,
8417
- template,
8418
- page: "tracer",
8419
- tracerId,
8420
- tracers: [tracerId],
8421
- activeTracer: tracerId,
8422
- docsRoutes
8423
- })
8424
- }));
8425
- });
8426
- app.get("/templates/:templateId/editor/tracer/:tracerId/history", async (c) => {
8427
- const customer = await requireBrowserCustomer(c);
8428
- if (!customer) {
8429
- return c.json({ error: "Unauthorized" }, 401);
8430
- }
8431
- const template = templateRegistry.get(c.req.param("templateId"));
8432
- if (!template) {
8433
- return c.json({ error: "Template not found" }, 404);
8434
- }
8435
- const tracerId = c.req.param("tracerId");
8436
- const query = apiCallHistoryQuerySchema.parse({
8437
- start_time: c.req.query("start_time"),
8438
- end_time: c.req.query("end_time"),
8439
- cursor: c.req.query("cursor"),
8440
- limit: c.req.query("limit")
8441
- });
8442
- const limit = clampPageLimit(query.limit, 250);
8443
- const entries = (await buildJobRunHistoryEntries({
8444
- customerId: customer.id,
8445
- startTime: query.start_time,
8446
- endTime: query.end_time,
8447
- templateId: template.id,
8448
- tracers: [tracerId],
8449
- includePrimitiveJobs: true,
8450
- entries: await apiCallHistoryStore.listEntries({
8451
- customerId: customer.id,
8452
- startTime: query.start_time,
8453
- endTime: query.end_time
8454
- })
8455
- })).filter((entry) => doesTracerMatchSelection(entry.tracer, [tracerId]) && matchesEditorApiHistoryPath(entry.path, template));
8456
- const filteredEntries = parseCreatedAtIdCursor(query.cursor)
8457
- ? entries.filter((entry) => (Date.parse(entry.createdAt) < Date.parse(parseCreatedAtIdCursor(query.cursor).createdAt)
8458
- || (entry.createdAt === parseCreatedAtIdCursor(query.cursor).createdAt && entry.id < parseCreatedAtIdCursor(query.cursor).id)))
8459
- : entries;
8460
- const page = paginateDescendingByCreatedAt(filteredEntries, limit);
8461
- return c.json({
8462
- entries: page.items,
8463
- storage_driver: config.STORAGE_DRIVER,
8464
- template_id: template.id,
8465
- tracer_id: tracerId,
8466
- next_cursor: page.nextCursor
8467
- });
8468
- });
8469
8157
  app.get("/calendar", async (c) => {
8470
8158
  const customer = await requireBrowserCustomer(c);
8471
8159
  if (!customer) {
@@ -9338,7 +9026,7 @@ app.post(`${ADMIN_TEMPLATES_PREFIX}/releases/:releaseId/activate`, async (c) =>
9338
9026
  // Behavior: waits for the download to finish (up to WAIT_TIMEOUT_MS), then
9339
9027
  // creates a Fork with a decomposed composition ready to open in the editor.
9340
9028
  // Returns { fork_id, editor_url, video_id } in a single response.
9341
- app.post(`${API_PREFIX}/admin/hyperframes/tiktok-submissions`, async (c) => {
9029
+ app.post(`${API_PREFIX}/admin/tiktok-submissions`, async (c) => {
9342
9030
  try {
9343
9031
  requireSuperagency(c);
9344
9032
  }
@@ -9525,11 +9213,11 @@ async function ensureSubmittedVideoReadyThenFork(input) {
9525
9213
  };
9526
9214
  }
9527
9215
  function buildForkEditorUrl(publicBaseUrl, fork) {
9528
- const path = `/hyperframes/${encodeURIComponent(fork.sourceSlugId)}/editor?fork=${encodeURIComponent(fork.id)}`;
9216
+ const path = `/editor/${encodeURIComponent(fork.sourceSlugId)}?fork=${encodeURIComponent(fork.id)}`;
9529
9217
  return publicBaseUrl ? `${publicBaseUrl.replace(/\/$/, "")}${path}` : path;
9530
9218
  }
9531
9219
  function buildTikTokEditorUrl(publicBaseUrl, slugId) {
9532
- const path = `/hyperframes/${encodeURIComponent(slugId)}/editor`;
9220
+ const path = `/editor/${encodeURIComponent(slugId)}`;
9533
9221
  return publicBaseUrl ? `${publicBaseUrl.replace(/\/$/, "")}${path}` : path;
9534
9222
  }
9535
9223
  function buildRawTikTokCompositionHtml(input) {
@@ -9554,7 +9242,7 @@ function buildRawTikTokCompositionHtml(input) {
9554
9242
  </body>
9555
9243
  </html>`;
9556
9244
  }
9557
- app.get(`${API_PREFIX}/admin/hyperframes/tiktok-submissions`, async (c) => {
9245
+ app.get(`${API_PREFIX}/admin/tiktok-submissions`, async (c) => {
9558
9246
  try {
9559
9247
  requireSuperagency(c);
9560
9248
  }
@@ -12306,7 +11994,7 @@ function primitiveAliasPathForKind(kind) {
12306
11994
  case "video_render_slides":
12307
11995
  return `${PRIMITIVES_PREFIX}/videos/render-slides`;
12308
11996
  case "hyperframes_render":
12309
- return `${PRIMITIVES_PREFIX}/hyperframes/render`;
11997
+ return `${PRIMITIVES_PREFIX}/timeline/render`;
12310
11998
  case "video_normalize":
12311
11999
  return `${PRIMITIVES_PREFIX}/videos/normalize`;
12312
12000
  case "video_download":
@@ -12952,7 +12640,7 @@ app.post(`${PRIMITIVES_PREFIX}/images/remove-background`, async (c) => createPri
12952
12640
  app.post(`${PRIMITIVES_PREFIX}/videos/generate`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_generate", operationName: "run" }));
12953
12641
  app.post(`${PRIMITIVES_PREFIX}/videos/download`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_download", operationName: "run" }));
12954
12642
  app.post(`${PRIMITIVES_PREFIX}/videos/render-slides`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_render_slides", operationName: "run" }));
12955
- app.post(`${PRIMITIVES_PREFIX}/hyperframes/render`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:hyperframes_render", operationName: "run" }));
12643
+ app.post(`${PRIMITIVES_PREFIX}/timeline/render`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:hyperframes_render", operationName: "run" }));
12956
12644
  app.post(`${PRIMITIVES_PREFIX}/videos/normalize`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_normalize", operationName: "run" }));
12957
12645
  app.post(`${PRIMITIVES_PREFIX}/videos/trim`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_trim", operationName: "run" }));
12958
12646
  app.post(`${PRIMITIVES_PREFIX}/videos/extract-audio`, async (c) => createPrimitiveJob(c, { primitiveId: "primitive:video_extract_audio", operationName: "run" }));