@mevdragon/vidfarm-devcli 0.20.6 → 0.20.9

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 (157) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +15 -1
  2. package/.agents/skills/vidfarm-director/SKILL.md +106 -0
  3. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  4. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  5. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  6. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  7. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +245 -0
  9. package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
  10. package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
  11. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  12. package/.agents/skills/vidfarm-director/references/primitives.md +308 -0
  13. package/SKILL.director.md +517 -250
  14. package/dist/src/cli.js +563 -99
  15. package/dist/src/devcli/clips.js +29 -12
  16. package/dist/src/devcli/doctor.js +2 -2
  17. package/dist/src/devcli/local-backend.js +46 -2
  18. package/dist/src/services/clip-curation/cost.js +5 -1
  19. package/dist/src/services/clip-curation/gemini.js +14 -2
  20. package/dist/src/services/clip-curation/hunt.js +56 -0
  21. package/dist/src/services/clip-curation/index.js +1 -1
  22. package/dist/src/services/clip-curation/scan.js +5 -3
  23. package/package.json +30 -10
  24. package/SKILL.platform.md +0 -432
  25. package/demo/README.md +0 -28
  26. package/demo/dist/app.css +0 -1
  27. package/demo/dist/app.js +0 -1850
  28. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  29. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  30. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  31. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  32. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  33. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  34. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  35. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  36. package/demo/dist/favicon.ico +0 -0
  37. package/demo/dist/icons/timeline/audio.svg +0 -7
  38. package/demo/dist/icons/timeline/captions.svg +0 -5
  39. package/demo/dist/icons/timeline/composition.svg +0 -12
  40. package/demo/dist/icons/timeline/image.svg +0 -18
  41. package/demo/dist/icons/timeline/music.svg +0 -10
  42. package/demo/dist/icons/timeline/text.svg +0 -3
  43. package/demo/dist/index.html +0 -15
  44. package/dist/src/account-pages-legacy.js +0 -9396
  45. package/dist/src/account-pages.js +0 -61
  46. package/dist/src/app.js +0 -21603
  47. package/dist/src/composition-runtime.js +0 -1053
  48. package/dist/src/config.js +0 -217
  49. package/dist/src/context.js +0 -447
  50. package/dist/src/dev-app-legacy.js +0 -739
  51. package/dist/src/dev-app.js +0 -6
  52. package/dist/src/devcli/migrate-local.js +0 -140
  53. package/dist/src/devcli/sync.js +0 -368
  54. package/dist/src/domain.js +0 -5
  55. package/dist/src/editor-chat-history.js +0 -82
  56. package/dist/src/editor-chat.js +0 -828
  57. package/dist/src/editor-dark-theme.js +0 -1128
  58. package/dist/src/frontend/debug.js +0 -71
  59. package/dist/src/frontend/discover-client.js +0 -130
  60. package/dist/src/frontend/discover-store.js +0 -23
  61. package/dist/src/frontend/file-directory.js +0 -1018
  62. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  63. package/dist/src/frontend/homepage-client.js +0 -446
  64. package/dist/src/frontend/homepage-shared.js +0 -201
  65. package/dist/src/frontend/homepage-store.js +0 -66
  66. package/dist/src/frontend/homepage-view.js +0 -705
  67. package/dist/src/frontend/page-runtime-client.js +0 -132
  68. package/dist/src/frontend/page-runtime-store.js +0 -9
  69. package/dist/src/frontend/sentry.js +0 -42
  70. package/dist/src/frontend/template-editor-chat.js +0 -4181
  71. package/dist/src/help-page.js +0 -346
  72. package/dist/src/homepage.js +0 -1458
  73. package/dist/src/index.js +0 -16
  74. package/dist/src/instrument.js +0 -30
  75. package/dist/src/landing-page.js +0 -384
  76. package/dist/src/page-runtime.js +0 -2
  77. package/dist/src/page-shell.js +0 -1452
  78. package/dist/src/primitive-context.js +0 -416
  79. package/dist/src/primitive-registry.js +0 -3940
  80. package/dist/src/primitive-sdk.js +0 -4
  81. package/dist/src/primitives/hyperframes-media.js +0 -108
  82. package/dist/src/react-page-shell.js +0 -35
  83. package/dist/src/ready-post-schedule-component.js +0 -1540
  84. package/dist/src/registry.js +0 -296
  85. package/dist/src/reskin/agency-page.js +0 -299
  86. package/dist/src/reskin/calendar-page.js +0 -568
  87. package/dist/src/reskin/chat-page.js +0 -942
  88. package/dist/src/reskin/discover-page.js +0 -1788
  89. package/dist/src/reskin/document.js +0 -1587
  90. package/dist/src/reskin/help-page.js +0 -357
  91. package/dist/src/reskin/index-page.js +0 -62
  92. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  93. package/dist/src/reskin/inpaint-page.js +0 -2554
  94. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  95. package/dist/src/reskin/job-runs-page.js +0 -477
  96. package/dist/src/reskin/library-page.js +0 -1634
  97. package/dist/src/reskin/login-page.js +0 -262
  98. package/dist/src/reskin/portfolio-page.js +0 -687
  99. package/dist/src/reskin/pricing-page.js +0 -390
  100. package/dist/src/reskin/settings-page.js +0 -732
  101. package/dist/src/reskin/theme.js +0 -711
  102. package/dist/src/runtime.js +0 -35
  103. package/dist/src/services/api-call-history.js +0 -249
  104. package/dist/src/services/auth.js +0 -152
  105. package/dist/src/services/billing-pricing.js +0 -39
  106. package/dist/src/services/billing.js +0 -241
  107. package/dist/src/services/cast.js +0 -127
  108. package/dist/src/services/chat-threads.js +0 -92
  109. package/dist/src/services/clip-records.js +0 -250
  110. package/dist/src/services/clip-search.js +0 -77
  111. package/dist/src/services/clip-vectors.js +0 -125
  112. package/dist/src/services/composition-sanitize.js +0 -124
  113. package/dist/src/services/composition-watch.js +0 -79
  114. package/dist/src/services/elevenlabs.js +0 -222
  115. package/dist/src/services/file-directory.js +0 -117
  116. package/dist/src/services/fork-access.js +0 -93
  117. package/dist/src/services/fork-manifest.js +0 -42
  118. package/dist/src/services/ghostcut.js +0 -179
  119. package/dist/src/services/hyperframes.js +0 -3654
  120. package/dist/src/services/job-capacity.js +0 -14
  121. package/dist/src/services/job-logs.js +0 -197
  122. package/dist/src/services/jobs.js +0 -136
  123. package/dist/src/services/local-dynamo.js +0 -0
  124. package/dist/src/services/media-processing.js +0 -766
  125. package/dist/src/services/primitive-media-lambda.js +0 -280
  126. package/dist/src/services/providers.js +0 -2748
  127. package/dist/src/services/rate-limits.js +0 -262
  128. package/dist/src/services/scene-annotations.js +0 -32
  129. package/dist/src/services/serverless-auth.js +0 -382
  130. package/dist/src/services/serverless-jobs.js +0 -1084
  131. package/dist/src/services/serverless-provider-keys.js +0 -409
  132. package/dist/src/services/serverless-records.js +0 -1515
  133. package/dist/src/services/serverless-template-configs.js +0 -75
  134. package/dist/src/services/storage.js +0 -461
  135. package/dist/src/services/swipe-customize.js +0 -437
  136. package/dist/src/services/template-certification.js +0 -413
  137. package/dist/src/services/template-loader.js +0 -99
  138. package/dist/src/services/template-runtime-bundles.js +0 -217
  139. package/dist/src/services/template-sources.js +0 -1017
  140. package/dist/src/services/upstream.js +0 -248
  141. package/dist/src/services/video-normalization.js +0 -2
  142. package/dist/src/services/webhooks.js +0 -62
  143. package/dist/src/template-editor-pages.js +0 -2576
  144. package/dist/src/template-editor-shell.js +0 -2893
  145. package/dist/src/template-sdk.js +0 -4
  146. package/dist/src/worker.js +0 -17
  147. package/public/assets/discover-client-app.js +0 -1
  148. package/public/assets/file-directory-app.js +0 -3
  149. package/public/assets/homepage-app.js +0 -54
  150. package/public/assets/homepage-client-app.js +0 -80
  151. package/public/assets/page-runtime-client-app.js +0 -94
  152. package/public/assets/placeholders/scene-placeholder.png +0 -0
  153. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  154. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  155. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  156. package/src/assets/favicon.ico +0 -0
  157. package/src/assets/logo-vidfarm.png +0 -0
@@ -1,124 +0,0 @@
1
- import { createStore } from "zustand/vanilla";
2
- export const FLOCKPOSTER_CACHE_STORAGE_KEY = "vidfarm:flockposter-cache:v1";
3
- const PUBLIC_SCOPE = "public";
4
- const emptySnapshot = {
5
- channels: [],
6
- posts: [],
7
- channelsUpdatedAt: null,
8
- calendarUpdatedAt: null,
9
- calendarRangeStart: null,
10
- calendarRangeEnd: null
11
- };
12
- export function getFlockPosterCacheStorageKey(userId) {
13
- return `${FLOCKPOSTER_CACHE_STORAGE_KEY}_${userId?.trim() || PUBLIC_SCOPE}`;
14
- }
15
- export function readFlockPosterCacheSnapshot(userId) {
16
- if (typeof window === "undefined") {
17
- return emptySnapshot;
18
- }
19
- try {
20
- const raw = window.localStorage.getItem(getFlockPosterCacheStorageKey(userId));
21
- if (!raw) {
22
- return emptySnapshot;
23
- }
24
- const parsed = JSON.parse(raw);
25
- if (!parsed || typeof parsed !== "object") {
26
- return emptySnapshot;
27
- }
28
- return {
29
- channels: Array.isArray(parsed.channels) ? parsed.channels : [],
30
- posts: Array.isArray(parsed.posts) ? parsed.posts : [],
31
- channelsUpdatedAt: typeof parsed.channelsUpdatedAt === "string" ? parsed.channelsUpdatedAt : null,
32
- calendarUpdatedAt: typeof parsed.calendarUpdatedAt === "string" ? parsed.calendarUpdatedAt : null,
33
- calendarRangeStart: typeof parsed.calendarRangeStart === "string" ? parsed.calendarRangeStart : null,
34
- calendarRangeEnd: typeof parsed.calendarRangeEnd === "string" ? parsed.calendarRangeEnd : null
35
- };
36
- }
37
- catch {
38
- return emptySnapshot;
39
- }
40
- }
41
- function writeFlockPosterCacheSnapshot(snapshot, userId) {
42
- if (typeof window === "undefined") {
43
- return;
44
- }
45
- try {
46
- window.localStorage.setItem(getFlockPosterCacheStorageKey(userId), JSON.stringify(snapshot));
47
- }
48
- catch {
49
- // Ignore storage failures and keep in-memory state only.
50
- }
51
- }
52
- export function createFlockPosterCacheStore(userId) {
53
- const initial = readFlockPosterCacheSnapshot(userId);
54
- return createStore((set, get) => ({
55
- ...initial,
56
- setChannelsData: (channels) => {
57
- const current = get();
58
- const next = {
59
- ...current,
60
- channels: [
61
- ...current.channels.filter(isEmailChannel),
62
- ...channels.filter((channel) => !isEmailChannel(channel))
63
- ],
64
- channelsUpdatedAt: new Date().toISOString()
65
- };
66
- writeFlockPosterCacheSnapshot(next, userId);
67
- set(next);
68
- },
69
- setCalendarData: (input) => {
70
- const current = get();
71
- const mergedChannels = dedupeChannels([...current.channels, ...input.channels]);
72
- const mergedPosts = input.replaceRange
73
- ? mergePostsForRange(current.posts, input.posts, input.rangeStart, input.rangeEnd)
74
- : dedupePosts([...current.posts, ...input.posts]);
75
- const next = {
76
- ...current,
77
- channels: mergedChannels,
78
- posts: mergedPosts,
79
- channelsUpdatedAt: new Date().toISOString(),
80
- calendarUpdatedAt: new Date().toISOString(),
81
- calendarRangeStart: expandRangeStart(current.calendarRangeStart, input.rangeStart),
82
- calendarRangeEnd: expandRangeEnd(current.calendarRangeEnd, input.rangeEnd)
83
- };
84
- writeFlockPosterCacheSnapshot(next, userId);
85
- set(next);
86
- }
87
- }));
88
- }
89
- function dedupeChannels(channels) {
90
- return [...new Map(channels.map((channel) => [channel.id, channel])).values()];
91
- }
92
- function isEmailChannel(channel) {
93
- return channel.id.startsWith("email:") || channel.platform?.toLowerCase() === "email";
94
- }
95
- function dedupePosts(posts) {
96
- return [...new Map(posts.map((post) => [post.id, post])).values()];
97
- }
98
- function mergePostsForRange(currentPosts, nextPosts, rangeStart, rangeEnd) {
99
- const startTime = Date.parse(rangeStart);
100
- const endTime = Date.parse(rangeEnd);
101
- if (Number.isNaN(startTime) || Number.isNaN(endTime)) {
102
- return dedupePosts([...currentPosts, ...nextPosts]);
103
- }
104
- return dedupePosts([
105
- ...currentPosts.filter((post) => {
106
- const scheduledTime = Date.parse(post.scheduledAt);
107
- return Number.isNaN(scheduledTime) || scheduledTime < startTime || scheduledTime > endTime;
108
- }),
109
- ...nextPosts
110
- ]);
111
- }
112
- function expandRangeStart(current, next) {
113
- if (!current) {
114
- return next;
115
- }
116
- return Date.parse(next) < Date.parse(current) ? next : current;
117
- }
118
- function expandRangeEnd(current, next) {
119
- if (!current) {
120
- return next;
121
- }
122
- return Date.parse(next) > Date.parse(current) ? next : current;
123
- }
124
- //# sourceMappingURL=flockposter-cache-store.js.map
@@ -1,446 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import "./sentry.js";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { hydrateRoot } from "react-dom/client";
5
- import * as Sentry from "@sentry/react";
6
- import { useStore } from "zustand";
7
- import { debugError, debugLog, installGlobalDebugHooks } from "./debug.js";
8
- import { HomepageShell } from "./homepage-view.js";
9
- import { createHomepageStore } from "./homepage-store.js";
10
- installGlobalDebugHooks();
11
- const bookmarkStorageKey = "vidfarm:discover-bookmarks:v1";
12
- const publicBookmarkScope = "public";
13
- function readBoot() {
14
- const element = document.getElementById("homepage-boot");
15
- if (!element?.textContent) {
16
- return {
17
- templates: [],
18
- account: { isLoggedIn: false },
19
- loadEndpoint: "/discover/feed",
20
- skillEndpointPrefix: "/discover/skills/"
21
- };
22
- }
23
- return JSON.parse(element.textContent);
24
- }
25
- function resolveBookmarkStorageKey(userId) {
26
- return `${bookmarkStorageKey}_${userId?.trim() || publicBookmarkScope}`;
27
- }
28
- function readBookmarks(userId) {
29
- try {
30
- const raw = window.localStorage.getItem(resolveBookmarkStorageKey(userId));
31
- if (!raw) {
32
- return new Set();
33
- }
34
- const parsed = JSON.parse(raw);
35
- if (!Array.isArray(parsed)) {
36
- return new Set();
37
- }
38
- return new Set(parsed.filter((value) => typeof value === "string"));
39
- }
40
- catch {
41
- return new Set();
42
- }
43
- }
44
- function writeBookmarks(bookmarks, userId) {
45
- try {
46
- window.localStorage.setItem(resolveBookmarkStorageKey(userId), JSON.stringify(Array.from(bookmarks)));
47
- }
48
- catch {
49
- // Ignore storage failures and keep in-memory state.
50
- }
51
- }
52
- function filterTemplates(input) {
53
- const query = input.searchQuery.trim().toLowerCase();
54
- return input.templates.filter((template) => {
55
- const matchesSearch = !query || `${template.templateId} ${template.title} ${template.slugId} ${template.difficulty}`.toLowerCase().includes(query);
56
- const matchesBookmarks = !input.bookmarksOnly || input.bookmarks.has(template.templateId);
57
- return matchesSearch && matchesBookmarks;
58
- });
59
- }
60
- // The swipe API serializes snake_case; the store/view use camelCase SwipeCard.
61
- function mapSwipeCard(raw) {
62
- return {
63
- id: String(raw?.id ?? ""),
64
- templateId: String(raw?.template_id ?? ""),
65
- forkId: raw?.fork_id ?? null,
66
- sourceTitle: raw?.source_title ?? null,
67
- title: String(raw?.title ?? raw?.source_title ?? "Untitled"),
68
- caption: String(raw?.caption ?? ""),
69
- pinnedComment: raw?.pinned_comment ?? null,
70
- previewUrl: raw?.preview_url ?? null,
71
- durationSeconds: typeof raw?.duration_seconds === "number" ? raw.duration_seconds : null,
72
- status: String(raw?.status ?? "ready")
73
- };
74
- }
75
- function HomepageClientApp({ boot }) {
76
- const store = useMemo(() => createHomepageStore(boot), [boot]);
77
- const templates = useStore(store, (state) => state.templates);
78
- const searchQuery = useStore(store, (state) => state.searchQuery);
79
- const bookmarksOnly = useStore(store, (state) => state.bookmarksOnly);
80
- const bookmarks = useStore(store, (state) => state.bookmarks);
81
- const loading = useStore(store, (state) => state.loading);
82
- const error = useStore(store, (state) => state.error);
83
- const view = useStore(store, (state) => state.view);
84
- const popularTemplates = useStore(store, (state) => state.popularTemplates);
85
- const popularLoading = useStore(store, (state) => state.popularLoading);
86
- const popularError = useStore(store, (state) => state.popularError);
87
- const swipeStack = useStore(store, (state) => state.swipeStack);
88
- const swipeLoading = useStore(store, (state) => state.swipeLoading);
89
- const swipeLoaded = useStore(store, (state) => state.swipeLoaded);
90
- const swipeError = useStore(store, (state) => state.swipeError);
91
- const swipeCustomizing = useStore(store, (state) => state.swipeCustomizing);
92
- const swipeTargetDepth = useStore(store, (state) => state.swipeTargetDepth);
93
- const swipeProductSet = useStore(store, (state) => state.swipeProductSet);
94
- const swipeRefilling = useStore(store, (state) => state.swipeRefilling);
95
- const swipeFillingRef = useRef(false);
96
- const [bookmarksReady, setBookmarksReady] = useState(false);
97
- const visibleTemplates = useMemo(() => filterTemplates({ templates, searchQuery, bookmarksOnly, bookmarks }), [templates, searchQuery, bookmarksOnly, bookmarks]);
98
- useEffect(() => {
99
- store.getState().setBookmarks(readBookmarks(boot.account.userId));
100
- setBookmarksReady(true);
101
- }, [boot.account.userId, store]);
102
- useEffect(() => {
103
- if (!bookmarksReady) {
104
- return;
105
- }
106
- writeBookmarks(bookmarks, boot.account.userId);
107
- }, [boot.account.userId, bookmarks, bookmarksReady]);
108
- const refreshFeed = useCallback(async (options) => {
109
- if (options?.showLoading) {
110
- store.getState().setLoading(true);
111
- }
112
- try {
113
- const response = await fetch(boot.loadEndpoint, {
114
- credentials: "same-origin",
115
- cache: "no-store",
116
- headers: { Accept: "application/json" }
117
- });
118
- const payload = await response.json().catch(() => ({}));
119
- if (!response.ok) {
120
- throw new Error(typeof payload.error === "string" && payload.error ? payload.error : "Unable to load formats.");
121
- }
122
- store.getState().setTemplates(Array.isArray(payload.templates) ? payload.templates : []);
123
- }
124
- catch (fetchError) {
125
- if (options?.showLoading) {
126
- store.getState().setError(fetchError instanceof Error ? fetchError.message : "Unable to load formats.");
127
- }
128
- else {
129
- // Background refresh failure keeps the current list on screen.
130
- debugError("homepage.feed_refresh_failed", { message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
131
- }
132
- }
133
- }, [boot.loadEndpoint, store]);
134
- useEffect(() => {
135
- if (boot.templates.length > 0) {
136
- return;
137
- }
138
- void refreshFeed({ showLoading: true });
139
- }, [boot.templates.length, refreshFeed]);
140
- // While one of the user's submissions is still downloading, poll the feed so
141
- // the "Processing" card flips to an openable template without a manual reload.
142
- const hasProcessingTemplates = templates.some((template) => template.status === "processing");
143
- useEffect(() => {
144
- if (!hasProcessingTemplates) {
145
- return;
146
- }
147
- const timer = window.setInterval(() => {
148
- void refreshFeed();
149
- }, 8000);
150
- return () => window.clearInterval(timer);
151
- }, [hasProcessingTemplates, refreshFeed]);
152
- // The curated Popular picks come from a separate endpoint and are the same
153
- // for everyone, so they're fetched lazily the first time the toggle is opened
154
- // (and re-fetched, without a spinner, while any pick is still downloading).
155
- const refreshPopular = useCallback(async (options) => {
156
- if (options?.showLoading) {
157
- store.getState().setPopularLoading(true);
158
- }
159
- try {
160
- const response = await fetch("/discover/popular", {
161
- credentials: "same-origin",
162
- cache: "no-store",
163
- headers: { Accept: "application/json" }
164
- });
165
- const payload = await response.json().catch(() => ({}));
166
- if (!response.ok) {
167
- throw new Error(typeof payload.error === "string" && payload.error ? payload.error : "Unable to load popular formats.");
168
- }
169
- store.getState().setPopularTemplates(Array.isArray(payload.templates) ? payload.templates : []);
170
- }
171
- catch (fetchError) {
172
- if (options?.showLoading) {
173
- store.getState().setPopularError(fetchError instanceof Error ? fetchError.message : "Unable to load popular formats.");
174
- }
175
- else {
176
- debugError("homepage.popular_refresh_failed", { message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
177
- }
178
- }
179
- }, [store]);
180
- const popularLoaded = useStore(store, (state) => state.popularLoaded);
181
- useEffect(() => {
182
- if (view !== "popular" || popularLoaded) {
183
- return;
184
- }
185
- void refreshPopular({ showLoading: true });
186
- }, [view, popularLoaded, refreshPopular]);
187
- const hasProcessingPopular = view === "popular" && popularTemplates.some((template) => template.status === "processing");
188
- useEffect(() => {
189
- if (!hasProcessingPopular) {
190
- return;
191
- }
192
- const timer = window.setInterval(() => {
193
- void refreshPopular();
194
- }, 8000);
195
- return () => window.clearInterval(timer);
196
- }, [hasProcessingPopular, refreshPopular]);
197
- // Swipe deck: the customer's own product, auto-recut from decomposed templates.
198
- const refreshSwipeStack = useCallback(async (options) => {
199
- if (options?.showLoading) {
200
- store.getState().setSwipeLoading(true);
201
- }
202
- try {
203
- const response = await fetch("/discover/swipe/stack", {
204
- credentials: "same-origin",
205
- cache: "no-store",
206
- headers: { Accept: "application/json" }
207
- });
208
- const payload = await response.json().catch(() => ({}));
209
- if (!response.ok) {
210
- throw new Error(typeof payload.error === "string" && payload.error ? payload.error : "Unable to load your swipe deck.");
211
- }
212
- const stack = Array.isArray(payload.stack) ? payload.stack.map(mapSwipeCard) : [];
213
- store.getState().setSwipeStack(stack, {
214
- customizing: typeof payload.customizing === "number" ? payload.customizing : 0,
215
- targetDepth: typeof payload.target_depth === "number" ? payload.target_depth : undefined,
216
- productSet: typeof payload.product_description_set === "boolean" ? payload.product_description_set : undefined
217
- });
218
- }
219
- catch (fetchError) {
220
- if (options?.showLoading) {
221
- store.getState().setSwipeError(fetchError instanceof Error ? fetchError.message : "Unable to load your swipe deck.");
222
- }
223
- else {
224
- debugError("homepage.swipe_refresh_failed", { message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
225
- }
226
- }
227
- }, [store]);
228
- // Progressively customize until the buffer hits target (server-capped), one
229
- // per request. Each finished variant streams into the deck as it lands.
230
- const fillSwipeDeck = useCallback(async () => {
231
- if (swipeFillingRef.current)
232
- return;
233
- swipeFillingRef.current = true;
234
- store.getState().setSwipeRefilling(true);
235
- try {
236
- for (let i = 0; i < 8; i += 1) {
237
- const response = await fetch("/discover/swipe/refill", {
238
- method: "POST",
239
- credentials: "same-origin",
240
- headers: { Accept: "application/json" }
241
- });
242
- const payload = await response.json().catch(() => ({}));
243
- if (!response.ok) {
244
- if (payload?.need_product_description) {
245
- store.getState().setSwipeMeta({ productSet: false });
246
- }
247
- else {
248
- store.getState().setSwipeError(typeof payload.error === "string" && payload.error ? payload.error : "Customization failed.");
249
- }
250
- break;
251
- }
252
- if (payload.filled && payload.record) {
253
- store.getState().appendSwipeCard(mapSwipeCard(payload.record));
254
- }
255
- if (!payload.filled) {
256
- break; // at_target or no_eligible
257
- }
258
- }
259
- }
260
- catch (fillError) {
261
- debugError("homepage.swipe_refill_failed", { message: fillError instanceof Error ? fillError.message : String(fillError) });
262
- }
263
- finally {
264
- swipeFillingRef.current = false;
265
- store.getState().setSwipeRefilling(false);
266
- // Re-sync the customizing/buffered counts after filling settles.
267
- void refreshSwipeStack();
268
- }
269
- }, [store, refreshSwipeStack]);
270
- // First open of Swipe → load the current deck, then top it up.
271
- useEffect(() => {
272
- if (view !== "swipe" || swipeLoaded) {
273
- return;
274
- }
275
- void (async () => {
276
- await refreshSwipeStack({ showLoading: true });
277
- const state = store.getState();
278
- if (state.swipeProductSet && state.swipeStack.length < state.swipeTargetDepth) {
279
- void fillSwipeDeck();
280
- }
281
- })();
282
- }, [view, swipeLoaded, refreshSwipeStack, fillSwipeDeck, store]);
283
- const handleSwipeApprove = useCallback(async (id) => {
284
- try {
285
- const response = await fetch(`/discover/swipe/${encodeURIComponent(id)}/approve`, {
286
- method: "POST",
287
- credentials: "same-origin",
288
- headers: { Accept: "application/json" }
289
- });
290
- const payload = await response.json().catch(() => ({}));
291
- if (!response.ok) {
292
- // The server retired this card (built by an older customize pipeline) —
293
- // drop it from the deck and pull a fresh one instead of jamming.
294
- if (payload && payload.superseded) {
295
- store.getState().removeSwipeCard(id);
296
- void fillSwipeDeck();
297
- return { ok: true };
298
- }
299
- return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to approve." };
300
- }
301
- store.getState().removeSwipeCard(id);
302
- void fillSwipeDeck(); // +1 fresh at the tail
303
- return { ok: true };
304
- }
305
- catch (approveError) {
306
- return { ok: false, error: approveError instanceof Error ? approveError.message : "Unable to approve." };
307
- }
308
- }, [store, fillSwipeDeck]);
309
- const handleSwipeReject = useCallback(async (id) => {
310
- try {
311
- const response = await fetch(`/discover/swipe/${encodeURIComponent(id)}/reject`, {
312
- method: "POST",
313
- credentials: "same-origin",
314
- headers: { Accept: "application/json" }
315
- });
316
- const payload = await response.json().catch(() => ({}));
317
- if (!response.ok) {
318
- if (payload && payload.superseded) {
319
- store.getState().removeSwipeCard(id);
320
- void fillSwipeDeck();
321
- return { ok: true };
322
- }
323
- return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to reject." };
324
- }
325
- store.getState().removeSwipeCard(id);
326
- void fillSwipeDeck(); // +1 fresh at the tail
327
- return { ok: true };
328
- }
329
- catch (rejectError) {
330
- return { ok: false, error: rejectError instanceof Error ? rejectError.message : "Unable to reject." };
331
- }
332
- }, [store, fillSwipeDeck]);
333
- const handleDeleteTemplate = useCallback(async (templateId) => {
334
- try {
335
- const response = await fetch(`/discover/templates/${encodeURIComponent(templateId)}`, {
336
- method: "DELETE",
337
- credentials: "same-origin",
338
- headers: { Accept: "application/json" }
339
- });
340
- const payload = await response.json().catch(() => ({}));
341
- if (!response.ok) {
342
- return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to delete this template." };
343
- }
344
- debugLog("homepage.template_deleted", { template_id: templateId });
345
- await refreshFeed();
346
- return { ok: true };
347
- }
348
- catch (deleteError) {
349
- return { ok: false, error: deleteError instanceof Error ? deleteError.message : "Unable to delete this template." };
350
- }
351
- }, [refreshFeed]);
352
- const handleAddTemplate = useCallback(async (submission) => {
353
- try {
354
- // Upload path: presign → PUT the bytes (straight to S3, or the local
355
- // multipart fallback) → finalize with the storage key. The URL path
356
- // posts source_url directly, exactly as before.
357
- let uploadRef = null;
358
- if (submission.file) {
359
- const file = submission.file;
360
- const presignResponse = await fetch("/discover/templates/upload/presign", {
361
- method: "POST",
362
- credentials: "same-origin",
363
- headers: { "content-type": "application/json", Accept: "application/json" },
364
- body: JSON.stringify({
365
- file_name: file.name,
366
- content_type: file.type || null,
367
- size_bytes: file.size
368
- })
369
- });
370
- const presign = await presignResponse.json().catch(() => ({}));
371
- if (!presignResponse.ok) {
372
- return { ok: false, error: presign.error || "Unable to start the video upload." };
373
- }
374
- if (presign.transport === "presigned" && presign.upload?.url && presign.storage_key) {
375
- const put = await fetch(presign.upload.url, {
376
- method: presign.upload.method || "PUT",
377
- headers: presign.upload.headers || {},
378
- body: file
379
- });
380
- if (!put.ok) {
381
- return { ok: false, error: `Video upload failed (${put.status}). Try again.` };
382
- }
383
- uploadRef = { storage_key: presign.storage_key, file_name: presign.file_name || file.name };
384
- }
385
- else {
386
- const form = new FormData();
387
- form.append("file", file, presign.file_name || file.name);
388
- const direct = await fetch(presign.upload?.url || "/discover/templates/upload", {
389
- method: "POST",
390
- credentials: "same-origin",
391
- body: form
392
- });
393
- const directPayload = await direct.json().catch(() => ({}));
394
- if (!direct.ok || !directPayload.storage_key) {
395
- return { ok: false, error: directPayload.error || "Video upload failed. Try again." };
396
- }
397
- uploadRef = { storage_key: directPayload.storage_key, file_name: directPayload.file_name || file.name };
398
- }
399
- }
400
- const response = await fetch("/discover/templates", {
401
- method: "POST",
402
- credentials: "same-origin",
403
- headers: { "content-type": "application/json", Accept: "application/json" },
404
- body: JSON.stringify(uploadRef
405
- ? {
406
- upload: uploadRef,
407
- title: submission.title || null,
408
- tagline: submission.tagline || null,
409
- notes: submission.notes || null
410
- }
411
- : {
412
- source_url: submission.sourceUrl,
413
- tagline: submission.tagline || null,
414
- notes: submission.notes || null
415
- })
416
- });
417
- const payload = await response.json().catch(() => ({}));
418
- if (!response.ok) {
419
- return { ok: false, error: typeof payload.error === "string" && payload.error ? payload.error : "Unable to add that video." };
420
- }
421
- debugLog("homepage.template_added", uploadRef
422
- ? { storage_key: uploadRef.storage_key }
423
- : { source_url: submission.sourceUrl });
424
- await refreshFeed();
425
- return { ok: true };
426
- }
427
- catch (submitError) {
428
- return { ok: false, error: submitError instanceof Error ? submitError.message : "Unable to add that video." };
429
- }
430
- }, [refreshFeed]);
431
- return (_jsx(HomepageShell, { account: boot.account, templates: templates, visibleTemplates: visibleTemplates, searchQuery: searchQuery, bookmarksOnly: bookmarksOnly, bookmarks: bookmarks, loading: loading, error: error, view: view, popularTemplates: popularTemplates, popularLoading: popularLoading, popularError: popularError, swipeStack: swipeStack, swipeLoading: swipeLoading, swipeLoaded: swipeLoaded, swipeError: swipeError, swipeCustomizing: swipeCustomizing, swipeTargetDepth: swipeTargetDepth, swipeProductSet: swipeProductSet, swipeRefilling: swipeRefilling, skillEndpointPrefix: boot.skillEndpointPrefix, onSearchQueryChange: (value) => store.getState().setSearchQuery(value), onBookmarksOnlyChange: (value) => store.getState().setBookmarksOnly(value), onViewChange: (value) => store.getState().setView(value), onToggleBookmark: (templateId) => store.getState().toggleBookmark(templateId), onAddTemplate: handleAddTemplate, onDeleteTemplate: handleDeleteTemplate, onSwipeApprove: handleSwipeApprove, onSwipeReject: handleSwipeReject }));
432
- }
433
- const boot = readBoot();
434
- const root = document.getElementById("homepage-root");
435
- debugLog("homepage.boot", {
436
- is_logged_in: boot.account?.isLoggedIn ?? false,
437
- template_count: boot.templates?.length ?? 0,
438
- load_endpoint: boot.loadEndpoint
439
- });
440
- if (root) {
441
- hydrateRoot(root, (_jsx(Sentry.ErrorBoundary, { fallback: _jsx(_Fragment, {}), onError: (error) => debugError("homepage.render_error", { message: error instanceof Error ? error.message : String(error) }), children: _jsx(HomepageClientApp, { boot: boot }) })));
442
- }
443
- else {
444
- debugError("homepage.no_root_element", {});
445
- }
446
- //# sourceMappingURL=homepage-client.js.map