@adminide-stack/yantra-mobile 12.0.121 → 12.0.123-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/lib/features/apps/appArt.js +1 -1
  2. package/lib/features/apps/appArt.js.map +1 -1
  3. package/lib/features/apps/mobileAppCatalog.js +15 -11
  4. package/lib/features/apps/mobileAppCatalog.js.map +1 -1
  5. package/lib/features/canvas/CanvasAgentChatPane.js +82 -20
  6. package/lib/features/canvas/CanvasAgentChatPane.js.map +1 -1
  7. package/lib/features/canvas/CanvasBoardsGrid.js +291 -100
  8. package/lib/features/canvas/CanvasBoardsGrid.js.map +1 -1
  9. package/lib/features/canvas/CanvasEditToolsPanel.js +105 -12
  10. package/lib/features/canvas/CanvasEditToolsPanel.js.map +1 -1
  11. package/lib/features/canvas/CanvasExperience.js +266 -68
  12. package/lib/features/canvas/CanvasExperience.js.map +1 -1
  13. package/lib/features/canvas/CanvasGeneratePanel.js +204 -66
  14. package/lib/features/canvas/CanvasGeneratePanel.js.map +1 -1
  15. package/lib/features/canvas/CanvasLibrarySheet.js +31 -13
  16. package/lib/features/canvas/CanvasLibrarySheet.js.map +1 -1
  17. package/lib/features/canvas/CanvasModelPicker.js +176 -0
  18. package/lib/features/canvas/CanvasModelPicker.js.map +1 -0
  19. package/lib/features/canvas/CanvasTasksPanel.js +325 -29
  20. package/lib/features/canvas/CanvasTasksPanel.js.map +1 -1
  21. package/lib/features/canvas/CanvasTemplatesPanel.js +5 -4
  22. package/lib/features/canvas/CanvasTemplatesPanel.js.map +1 -1
  23. package/lib/features/canvas/CanvasToolDock.js +137 -61
  24. package/lib/features/canvas/CanvasToolDock.js.map +1 -1
  25. package/lib/features/canvas/FrameExtractorExperience.js +33 -2
  26. package/lib/features/canvas/FrameExtractorExperience.js.map +1 -1
  27. package/lib/features/canvas/GeneratorReferenceSlots.js +197 -0
  28. package/lib/features/canvas/GeneratorReferenceSlots.js.map +1 -0
  29. package/lib/features/canvas/NativeCanvasViewer.js +89 -11
  30. package/lib/features/canvas/NativeCanvasViewer.js.map +1 -1
  31. package/lib/features/canvas/PromoContextFields.js +55 -2
  32. package/lib/features/canvas/PromoContextFields.js.map +1 -1
  33. package/lib/features/canvas/SetStudioExperience.js +323 -13
  34. package/lib/features/canvas/SetStudioExperience.js.map +1 -1
  35. package/lib/features/canvas/VideoEditExperience.js +1 -53
  36. package/lib/features/canvas/VideoEditExperience.js.map +1 -1
  37. package/lib/features/canvas/VideoEditorExperience.js +697 -506
  38. package/lib/features/canvas/VideoEditorExperience.js.map +1 -1
  39. package/lib/features/canvas/VideoGeneratorExperience.js +22 -5
  40. package/lib/features/canvas/VideoGeneratorExperience.js.map +1 -1
  41. package/lib/features/canvas/canvasAgentChat.js +125 -11
  42. package/lib/features/canvas/canvasAgentChat.js.map +1 -1
  43. package/lib/features/canvas/canvasCore.js +117 -12
  44. package/lib/features/canvas/canvasCore.js.map +1 -1
  45. package/lib/features/canvas/canvasEditOps.js +148 -111
  46. package/lib/features/canvas/canvasEditOps.js.map +1 -1
  47. package/lib/features/canvas/canvasTaskSync.js +235 -0
  48. package/lib/features/canvas/canvasTaskSync.js.map +1 -0
  49. package/lib/features/canvas/canvasTasks.js +98 -11
  50. package/lib/features/canvas/canvasTasks.js.map +1 -1
  51. package/lib/features/canvas/createTiles.js +247 -0
  52. package/lib/features/canvas/createTiles.js.map +1 -0
  53. package/lib/features/canvas/deepLinks.js +83 -0
  54. package/lib/features/canvas/deepLinks.js.map +1 -0
  55. package/lib/features/canvas/frameExtract.js +13 -1
  56. package/lib/features/canvas/frameExtract.js.map +1 -1
  57. package/lib/features/canvas/generationModels.js +36 -8
  58. package/lib/features/canvas/generationModels.js.map +1 -1
  59. package/lib/features/canvas/generatorCommands.js +106 -0
  60. package/lib/features/canvas/generatorCommands.js.map +1 -0
  61. package/lib/features/canvas/generatorReferences.js +82 -0
  62. package/lib/features/canvas/generatorReferences.js.map +1 -0
  63. package/lib/features/canvas/homeWork.js +74 -0
  64. package/lib/features/canvas/homeWork.js.map +1 -0
  65. package/lib/features/canvas/linksRpc.js +130 -0
  66. package/lib/features/canvas/linksRpc.js.map +1 -0
  67. package/lib/features/canvas/mediaRecheck.js +176 -0
  68. package/lib/features/canvas/mediaRecheck.js.map +1 -0
  69. package/lib/features/canvas/mediaUrls.js +258 -0
  70. package/lib/features/canvas/mediaUrls.js.map +1 -0
  71. package/lib/features/canvas/nativeViewerRegistry.js.map +1 -1
  72. package/lib/features/canvas/promoProjectImport.js +80 -13
  73. package/lib/features/canvas/promoProjectImport.js.map +1 -1
  74. package/lib/features/canvas/promoVideo.js +106 -15
  75. package/lib/features/canvas/promoVideo.js.map +1 -1
  76. package/lib/features/canvas/reframe.js +228 -0
  77. package/lib/features/canvas/reframe.js.map +1 -0
  78. package/lib/features/canvas/serverTasks.js +697 -0
  79. package/lib/features/canvas/serverTasks.js.map +1 -0
  80. package/lib/features/canvas/set/SetViewportWebView.js +207 -28
  81. package/lib/features/canvas/set/SetViewportWebView.js.map +1 -1
  82. package/lib/features/canvas/set/agent/prompt.js +66 -0
  83. package/lib/features/canvas/set/agent/prompt.js.map +1 -0
  84. package/lib/features/canvas/set/agent/setOps.js +203 -0
  85. package/lib/features/canvas/set/agent/setOps.js.map +1 -0
  86. package/lib/features/canvas/set/agent/setTurn.js +128 -0
  87. package/lib/features/canvas/set/agent/setTurn.js.map +1 -0
  88. package/lib/features/canvas/set/agent/snapshot.js +43 -0
  89. package/lib/features/canvas/set/agent/snapshot.js.map +1 -0
  90. package/lib/features/canvas/set/agent/tools.js +580 -0
  91. package/lib/features/canvas/set/agent/tools.js.map +1 -0
  92. package/lib/features/canvas/set/setProject.js +43 -4
  93. package/lib/features/canvas/set/setProject.js.map +1 -1
  94. package/lib/features/canvas/set/supplyLibrary.js +397 -0
  95. package/lib/features/canvas/set/supplyLibrary.js.map +1 -0
  96. package/lib/features/canvas/storageLibrary.js +113 -52
  97. package/lib/features/canvas/storageLibrary.js.map +1 -1
  98. package/lib/features/canvas/studioCatalog.js +15 -2
  99. package/lib/features/canvas/studioCatalog.js.map +1 -1
  100. package/lib/features/canvas/studioLooks.js +1 -8
  101. package/lib/features/canvas/studioLooks.js.map +1 -1
  102. package/lib/features/canvas/useBoardMediaRefresh.js +66 -0
  103. package/lib/features/canvas/useBoardMediaRefresh.js.map +1 -0
  104. package/lib/features/canvas/useMediaSource.js +84 -0
  105. package/lib/features/canvas/useMediaSource.js.map +1 -0
  106. package/lib/features/canvas/video3d.js +11 -10
  107. package/lib/features/canvas/video3d.js.map +1 -1
  108. package/lib/features/canvas/videoEditor/BlurBoxOverlay.js +46 -0
  109. package/lib/features/canvas/videoEditor/BlurBoxOverlay.js.map +1 -0
  110. package/lib/features/canvas/videoEditor/ExportTaskCard.js +134 -0
  111. package/lib/features/canvas/videoEditor/ExportTaskCard.js.map +1 -0
  112. package/lib/features/canvas/videoEditor/agent/AgentCutPane.js +45 -7
  113. package/lib/features/canvas/videoEditor/agent/AgentCutPane.js.map +1 -1
  114. package/lib/features/canvas/videoEditor/agent/agentLoop.js +4 -2
  115. package/lib/features/canvas/videoEditor/agent/agentLoop.js.map +1 -1
  116. package/lib/features/canvas/videoEditor/agent/attachments.js +304 -0
  117. package/lib/features/canvas/videoEditor/agent/attachments.js.map +1 -0
  118. package/lib/features/canvas/videoEditor/agent/brainClient.js +3 -2
  119. package/lib/features/canvas/videoEditor/agent/brainClient.js.map +1 -1
  120. package/lib/features/canvas/videoEditor/agent/cutTools.js +207 -7
  121. package/lib/features/canvas/videoEditor/agent/cutTools.js.map +1 -1
  122. package/lib/features/canvas/videoEditor/blurBox.js +34 -0
  123. package/lib/features/canvas/videoEditor/blurBox.js.map +1 -0
  124. package/lib/features/canvas/videoEditor/compileTimeline.js +668 -0
  125. package/lib/features/canvas/videoEditor/compileTimeline.js.map +1 -0
  126. package/lib/features/canvas/videoEditor/editorModel.js +56 -22
  127. package/lib/features/canvas/videoEditor/editorModel.js.map +1 -1
  128. package/lib/features/canvas/videoEditor/exportPipeline.js +77 -0
  129. package/lib/features/canvas/videoEditor/exportPipeline.js.map +1 -0
  130. package/lib/features/canvas/videoEditor/exportRenderer.js +19 -12
  131. package/lib/features/canvas/videoEditor/exportRenderer.js.map +1 -1
  132. package/lib/features/canvas/videoEditor/exportTask.js +216 -0
  133. package/lib/features/canvas/videoEditor/exportTask.js.map +1 -0
  134. package/lib/features/canvas/videoEditor/formWidgets.js +142 -0
  135. package/lib/features/canvas/videoEditor/formWidgets.js.map +1 -0
  136. package/lib/features/canvas/videoEditor/produce/ProduceVideoSheet.js +195 -0
  137. package/lib/features/canvas/videoEditor/produce/ProduceVideoSheet.js.map +1 -0
  138. package/lib/features/canvas/videoEditor/produce/produceScenes.js +158 -0
  139. package/lib/features/canvas/videoEditor/produce/produceScenes.js.map +1 -0
  140. package/lib/features/canvas/videoEditor/remoteMediaClient.js +17 -5
  141. package/lib/features/canvas/videoEditor/remoteMediaClient.js.map +1 -1
  142. package/lib/features/canvas/videoEditor/renderTimeline.js +207 -0
  143. package/lib/features/canvas/videoEditor/renderTimeline.js.map +1 -0
  144. package/lib/features/canvas/videoEditor/renewLinks.js +64 -0
  145. package/lib/features/canvas/videoEditor/renewLinks.js.map +1 -0
  146. package/lib/features/canvas/videoEditor/sceneLayers.js +101 -0
  147. package/lib/features/canvas/videoEditor/sceneLayers.js.map +1 -0
  148. package/lib/features/canvas/videoEditor/tools/ToolsDrawer.js +283 -0
  149. package/lib/features/canvas/videoEditor/tools/ToolsDrawer.js.map +1 -0
  150. package/lib/features/canvas/videoEditor/tools/catalog.js +576 -0
  151. package/lib/features/canvas/videoEditor/tools/catalog.js.map +1 -0
  152. package/lib/features/canvas/videoEditor/videoProjectsStore.js +27 -3
  153. package/lib/features/canvas/videoEditor/videoProjectsStore.js.map +1 -1
  154. package/lib/features/webbuilder/WebBuilderExperience.js +29 -1
  155. package/lib/features/webbuilder/WebBuilderExperience.js.map +1 -1
  156. package/lib/features/webbuilder/buildFragments.js +8 -4
  157. package/lib/features/webbuilder/buildFragments.js.map +1 -1
  158. package/lib/features/webbuilder/codezenHandoff.js +108 -0
  159. package/lib/features/webbuilder/codezenHandoff.js.map +1 -0
  160. package/lib/features/webbuilder/workflow.js.map +1 -1
  161. package/lib/hooks/useChatApi.js +1 -2
  162. package/lib/hooks/useChatApi.js.map +1 -1
  163. package/lib/screens/CanvasBoard/index.js +12 -1
  164. package/lib/screens/CanvasBoard/index.js.map +1 -1
  165. package/lib/screens/Home/HomeScreen.js.map +1 -1
  166. package/lib/screens/Home/components/HomeSuggestions.js +3 -3
  167. package/lib/screens/Home/components/HomeSuggestions.js.map +1 -1
  168. package/lib/screens/WebBuilder/index.js +4 -2
  169. package/lib/screens/WebBuilder/index.js.map +1 -1
  170. package/lib/screens/WebBuilderNative/index.js +29 -2
  171. package/lib/screens/WebBuilderNative/index.js.map +1 -1
  172. package/lib/screens/WebBuilderNative/useWebBuilderHost.js +30 -6
  173. package/lib/screens/WebBuilderNative/useWebBuilderHost.js.map +1 -1
  174. package/package.json +4 -4
  175. /package/lib/assets/apps/{paper2poster.webp → poster.webp} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverTasks.js","sources":["../../../src/features/canvas/serverTasks.ts"],"sourcesContent":["/**\n * Server-backed canvas tasks - native twin of surface-kit `tasks/serverJob`,\n * `tasks/remoteTasks`, `tasks/remoteRecord` and canvas `tasks/serverJobs`.\n *\n * A job is recorded on the Task API BEFORE its work is submitted, so the cdecli\n * agent that runs the work can report progress, the verdict and the result to\n * that row - and the result lands whether or not this phone is still open.\n * The exec request carries `job` ({@link SurfaceJobRef}); an older agent\n * ignores the field and the phone follows the work itself, as it always did.\n *\n * Rows are owner-scoped (`getSurfaceJobs` / `getSurfaceJob` answer with the\n * caller's own jobs), keyed `canvas:<local id>` exactly like the web canvas's,\n * so a run started on the web shows here and one started here shows there.\n *\n * Transport: the canvas session token (the surface JWT) against the backend\n * GraphQL the index URL names (`deriveBackendGraphqlUrl`). The report token\n * lives in memory only - never in AsyncStorage.\n *\n * Every call degrades: no session, an older backend (\"Cannot query field\"),\n * or no answer within two seconds, and the run goes out exactly as before.\n */\nimport type { CanvasTask } from './canvasTasks';\nimport {\n PRODUCE_STATUS_ACTION,\n PROMO_CONNECTOR,\n PROMO_POLL_EVERY_MS,\n promoRunTimeoutMs,\n type PromoRunOptions,\n} from './promoVideo';\nimport type { MediaConnector, RunMediaAction } from './videoEditor/mediaActions';\nimport {\n deriveBackendGraphqlUrl,\n runActionAcceptsJob,\n type RunMediaActionWithBody,\n} from './videoEditor/remoteMediaClient';\nimport { tokenClaims } from './sessionIdentity';\n\nexport const CANVAS_SURFACE = 'canvas';\nexport const PROMO_LANE = 'promo';\nexport const EXPORT_LANE = 'video-export';\nexport const LIPSYNC_LANE = 'lipsync';\nexport const SURFACE_JOB_KIND = 'surface-job';\n\n// ── The record ───────────────────────────────────────────────────────────────\n\nexport type JobState = 'running' | 'succeeded' | 'failed';\nexport type JobVerdictBy = 'agent' | 'browser' | 'deadline';\n\nexport interface JobStage {\n label: string;\n index: number;\n total: number;\n detail?: string;\n}\n\nexport interface JobResult {\n url: string;\n kind: 'image' | 'video' | 'audio' | 'text';\n title?: string;\n}\n\nexport interface JobInput {\n prompt?: string;\n model?: string;\n aspectRatio?: string;\n references?: string[];\n connector?: string;\n action?: string;\n params?: Record<string, unknown>;\n}\n\n/** A job as every store can express it, and as the Tasks panel reads it. */\nexport interface JobRecord {\n id: string;\n surface: string;\n lane: string;\n title: string;\n brief: string;\n state: JobState;\n createdAt: number;\n startedAt?: number;\n endedAt?: number;\n progress?: string;\n stage?: JobStage;\n results?: JobResult[];\n error?: string;\n delivered?: boolean;\n owner?: string;\n pageUrl?: string;\n input?: JobInput;\n handle?: Record<string, unknown>;\n remoteId?: string;\n deadlineAt?: number;\n agentOwned?: boolean;\n verdictBy?: JobVerdictBy;\n result?: unknown;\n hasResult?: boolean;\n resultUrl?: string;\n updatedAt?: number;\n}\n\n/** The server sweep's word for a job that ran past its deadline with nothing reporting on it. */\nexport const DEADLINE_VERDICT_ERROR = 'Stopped responding before it finished';\n/** Added to every lane's budget for its deadline. */\nexport const JOB_DEADLINE_GRACE_MS = 10 * 60_000;\n/** How long a handed-over job may go without the agent reporting before the phone follows it itself. */\nexport const AGENT_OWNED_GRACE_MS = 2 * 60_000;\n/** How long a job may wait on its own record before it is submitted without one. */\nexport const SERVER_JOB_BUDGET_MS = 2_000;\n/** A row recorded before deadlines existed reads as stale a day after it was created. */\nexport const LEGACY_JOB_DEADLINE_MS = 24 * 60 * 60_000;\n/** Lip sync: speech, then a provider queue measured at four minutes; the agent follows it for fifteen. */\nexport const LIPSYNC_BUDGET_MS = 15 * 60_000;\n\nexport const jobDeadlineAt = (startedAt: number, budgetMs: number): number =>\n startedAt + budgetMs + JOB_DEADLINE_GRACE_MS;\nexport const jobKey = (job: Pick<JobRecord, 'surface' | 'id'>): string => `${job.surface}:${job.id}`;\nexport const canvasJobKey = (localId: string): string => `${CANVAS_SURFACE}:${localId}`;\n\nexport function jobDeadline(job: Pick<JobRecord, 'deadlineAt' | 'createdAt'>): number {\n return job.deadlineAt ?? job.createdAt + LEGACY_JOB_DEADLINE_MS;\n}\n\n/** Running past its deadline: nothing is going to settle it any more. */\nexport function isStaleJob(job: Pick<JobRecord, 'state' | 'deadlineAt' | 'createdAt'>, now = Date.now()): boolean {\n return job.state === 'running' && now > jobDeadline(job);\n}\n\n/** Failed by the server's deadline sweep. */\nexport const isDeadlineVerdict = (record: { state: string; verdictBy?: string }): boolean =>\n record.state === 'failed' && record.verdictBy === 'deadline';\n\n/** The board a job's result belongs on, when its surface recorded one. */\nexport function jobBoardId(job: Pick<JobRecord, 'handle'>): string | undefined {\n const handle = job.handle;\n if (!handle) return undefined;\n if (typeof handle.boardId === 'string' && handle.boardId) return handle.boardId;\n return handle.kind === 'channel' && typeof handle.channelId === 'string' && handle.channelId\n ? handle.channelId\n : undefined;\n}\n\n/** A stable 24-hex ObjectId from a key (three FNV-1a passes) - identical to the kit's, so rows line up. */\nexport function objectIdFromKey(key: string): string {\n const fnv = (seed: number): string => {\n let h = seed >>> 0;\n for (let i = 0; i < key.length; i += 1) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h.toString(16).padStart(8, '0');\n };\n return fnv(0x811c9dc5) + fnv(0x9747b28c) + fnv(0x5bd1e995);\n}\n\nconst isWebUrl = (value: unknown): value is string => typeof value === 'string' && /^https?:\\/\\//i.test(value);\nconst WIRE_FIELD_MAX_CHARS = 16 * 1024;\nconst fits = (value: unknown): boolean => {\n try {\n return JSON.stringify(value).length <= WIRE_FIELD_MAX_CHARS;\n } catch {\n return false;\n }\n};\n\n/** The job as a client may write it: no server/agent-owned fields, web URLs only, bounded input. */\nexport function browserDetails(job: JobRecord): Record<string, unknown> {\n const {\n remoteId: _remoteId,\n agentOwned: _agentOwned,\n verdictBy: _verdictBy,\n result: _result,\n hasResult: _hasResult,\n resultUrl: _resultUrl,\n updatedAt: _updatedAt,\n results,\n pageUrl,\n input,\n handle,\n ...rest\n } = job;\n const webResults = results?.filter((r) => isWebUrl(r.url)).slice(0, 50);\n const keptInput = input\n ? fits(input)\n ? input\n : input.prompt\n ? { prompt: input.prompt.slice(0, 4_000) }\n : undefined\n : undefined;\n return {\n ...rest,\n ...(webResults?.length ? { results: webResults } : {}),\n ...(isWebUrl(pageUrl) ? { pageUrl } : {}),\n ...(keptInput ? { input: keptInput } : {}),\n ...(handle && fits(handle) ? { handle } : {}),\n };\n}\n\n/** What the panels render of a job, so an unchanged reading sends nothing. */\nexport function jobFingerprint(job: JobRecord): string {\n return JSON.stringify([\n job.state,\n job.title,\n job.brief,\n job.progress ?? '',\n job.stage ? [job.stage.index, job.stage.total, job.stage.label, job.stage.detail ?? ''] : null,\n job.results ?? [],\n job.error ?? '',\n job.delivered ?? false,\n job.startedAt ?? 0,\n job.endedAt ?? 0,\n ]);\n}\n\n/** The subset of a `Task` row the reader needs. */\nexport interface RemoteTaskRow {\n id?: string | null;\n title?: string | null;\n description?: string | null;\n status?: string | null;\n source?: { type?: string | null; id?: string | null; details?: unknown } | null;\n createdAt?: string | null;\n updatedAt?: string | number | null;\n}\n\nexport function jobStateFor(status: string | null | undefined): JobState {\n if (status === 'COMPLETED') return 'succeeded';\n if (status === 'IN_PROGRESS' || status === 'NOT_STARTED') return 'running';\n return 'failed';\n}\n\nconst num = (v: unknown): number | undefined => (typeof v === 'number' && Number.isFinite(v) ? v : undefined);\nconst str = (v: unknown): string | undefined => (typeof v === 'string' && v ? v : undefined);\n\nfunction timestampMs(value: unknown): number | undefined {\n if (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n if (typeof value !== 'string' || !value) return undefined;\n const ms = /^\\d+$/.test(value) ? Number(value) : Date.parse(value);\n return Number.isFinite(ms) ? ms : undefined;\n}\n\nfunction parseResults(v: unknown): JobResult[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: JobResult[] = [];\n for (const item of v) {\n if (!item || typeof item !== 'object') continue;\n const r = item as Record<string, unknown>;\n const url = str(r.url);\n if (!url) continue;\n const kind =\n r.kind === 'image' || r.kind === 'video' || r.kind === 'audio' || r.kind === 'text' ? r.kind : 'text';\n out.push({ url, kind, ...(str(r.title) ? { title: str(r.title) } : {}) });\n }\n return out.length ? out : undefined;\n}\n\nfunction parseStage(v: unknown): JobStage | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const s = v as Record<string, unknown>;\n const label = str(s.label);\n if (!label) return undefined;\n return {\n label,\n index: num(s.index) ?? 1,\n total: Math.max(1, num(s.total) ?? 1),\n ...(str(s.detail) ? { detail: str(s.detail) } : {}),\n };\n}\n\nfunction parseInput(v: unknown): JobInput | undefined {\n if (!v || typeof v !== 'object') return undefined;\n const i = v as Record<string, unknown>;\n const refs = Array.isArray(i.references)\n ? i.references.filter((r): r is string => typeof r === 'string' && !!r)\n : [];\n const input: JobInput = {\n ...(str(i.prompt) ? { prompt: str(i.prompt) } : {}),\n ...(str(i.model) ? { model: str(i.model) } : {}),\n ...(str(i.aspectRatio) ? { aspectRatio: str(i.aspectRatio) } : {}),\n ...(refs.length ? { references: refs } : {}),\n ...(str(i.connector) ? { connector: str(i.connector) } : {}),\n ...(str(i.action) ? { action: str(i.action) } : {}),\n ...(i.params && typeof i.params === 'object' ? { params: i.params as Record<string, unknown> } : {}),\n };\n return Object.keys(input).length ? input : undefined;\n}\n\n/** A job from a server row, or null when the row is some other kind of task. The row's status wins. */\nexport function jobFromTaskRow(row: RemoteTaskRow | null | undefined): JobRecord | null {\n const details = row?.source?.details;\n if (!details || typeof details !== 'object') return null;\n const d = details as Record<string, unknown>;\n if (d.kind !== SURFACE_JOB_KIND) return null;\n const id = str(d.id);\n const surface = str(d.surface);\n if (!id || !surface) return null;\n const stored = str(d.state);\n const state: JobState = row?.status\n ? jobStateFor(row.status)\n : stored === 'running' || stored === 'succeeded' || stored === 'failed'\n ? stored\n : 'running';\n const createdAt = num(d.createdAt) ?? timestampMs(row?.createdAt) ?? 0;\n const results = parseResults(d.results);\n const stage = parseStage(d.stage);\n const input = parseInput(d.input);\n const handle = d.handle && typeof d.handle === 'object' ? (d.handle as Record<string, unknown>) : undefined;\n const verdictBy =\n d.verdictBy === 'agent' || d.verdictBy === 'browser' || d.verdictBy === 'deadline' ? d.verdictBy : undefined;\n const updatedAt = timestampMs(row?.updatedAt);\n return {\n id,\n surface,\n lane: str(d.lane) ?? 'job',\n title: str(d.title) ?? str(row?.title) ?? 'Job',\n brief: str(d.brief) ?? str(row?.description) ?? '',\n state,\n createdAt,\n ...(num(d.startedAt) !== undefined ? { startedAt: num(d.startedAt) } : {}),\n ...(num(d.endedAt) !== undefined ? { endedAt: num(d.endedAt) } : {}),\n ...(str(d.progress) ? { progress: str(d.progress) } : {}),\n ...(stage ? { stage } : {}),\n ...(results ? { results } : {}),\n ...(str(d.error) ? { error: str(d.error) } : {}),\n ...(d.delivered === true ? { delivered: true } : {}),\n ...(str(d.owner) ? { owner: str(d.owner) } : {}),\n ...(str(d.pageUrl) ? { pageUrl: str(d.pageUrl) } : {}),\n ...(input ? { input } : {}),\n ...(handle ? { handle } : {}),\n ...(str(row?.id) ? { remoteId: str(row?.id) } : {}),\n ...(num(d.deadlineAt) !== undefined ? { deadlineAt: num(d.deadlineAt) } : {}),\n ...(d.agentOwned === true ? { agentOwned: true } : {}),\n ...(verdictBy ? { verdictBy } : {}),\n ...(d.result !== undefined && d.result !== null ? { result: d.result } : {}),\n ...(d.hasResult === true ? { hasResult: true } : {}),\n ...(str(d.resultUrl) ? { resultUrl: str(d.resultUrl) } : {}),\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n };\n}\n\n/** Newest first, with everything still running ahead of everything finished. */\nexport function sortJobs<T extends Pick<JobRecord, 'state' | 'createdAt'>>(jobs: T[]): T[] {\n return [...jobs].sort((a, b) => {\n const ra = a.state === 'running' ? 0 : 1;\n const rb = b.state === 'running' ? 0 : 1;\n return ra - rb || b.createdAt - a.createdAt;\n });\n}\n\n// ── Canvas tasks as jobs ─────────────────────────────────────────────────────\n\n/** Loose view of a store task: the export lane's extra fields are optional and read defensively. */\ntype AnyCanvasTask = CanvasTask & {\n step?: { index: number; total: number; label: string };\n owner?: string;\n deadlineAt?: number;\n verdictBy?: JobVerdictBy;\n};\n\nexport const isExportTask = (task: Pick<CanvasTask, 'kind'>): boolean =>\n (task.kind as string) === 'export' || (task.kind as string) === 'video-export';\n\n/** The job a store task is (web `canvasTaskToJob`). */\nexport function canvasTaskToJob(task: CanvasTask): JobRecord {\n const t = task as AnyCanvasTask;\n const isExport = isExportTask(task);\n const handle = (t.handle ?? {}) as unknown as Record<string, unknown>;\n const request = (t.request ?? {}) as unknown as Record<string, unknown>;\n const resultTitle = !isExport ? (t.result as { title?: string } | undefined)?.title : undefined;\n return {\n id: t.id,\n surface: CANVAS_SURFACE,\n lane: isExport ? EXPORT_LANE : PROMO_LANE,\n title: t.title,\n brief: t.brief,\n state: t.state === 'submitting' ? 'running' : t.state,\n createdAt: t.createdAt,\n ...(t.startedAt ? { startedAt: t.startedAt } : {}),\n ...(t.endedAt ? { endedAt: t.endedAt } : {}),\n ...(t.lastLine ? { progress: t.lastLine } : {}),\n ...(t.step ? { stage: { ...t.step } } : {}),\n ...(t.result?.url\n ? {\n results: [\n {\n url: t.result.url,\n kind: isExport && request.media === 'image' ? ('image' as const) : ('video' as const),\n ...(resultTitle ? { title: resultTitle } : {}),\n },\n ],\n }\n : {}),\n ...(t.error ? { error: t.error } : {}),\n ...(t.delivered ? { delivered: true } : {}),\n ...(t.owner ? { owner: t.owner } : {}),\n input: isExport ? { prompt: t.brief } : { prompt: t.brief, params: request },\n handle: {\n boardId: t.boardId,\n ...(typeof handle.projectId === 'string' ? { projectId: handle.projectId } : {}),\n ...(typeof handle.sandboxId === 'string' ? { sandboxId: handle.sandboxId } : {}),\n ...(typeof handle.runId === 'string' ? { runId: handle.runId } : {}),\n },\n ...(t.deadlineAt ? { deadlineAt: t.deadlineAt } : {}),\n ...(t.verdictBy ? { verdictBy: t.verdictBy } : {}),\n };\n}\n\n// ── The wire ─────────────────────────────────────────────────────────────────\n\n/** How the agent follows a SUBMIT/STATUS lane on its own (agent field names, snake_case). */\nexport interface FollowSpec {\n connector: string;\n action: string;\n params: Record<string, string>;\n static_params?: Record<string, unknown>;\n interval_ms: number;\n deadline_ms: number;\n state_key: string;\n succeeded: string[];\n failed: string[];\n}\n\nexport type JobExpectation = 'media' | 'result';\n\n/** The `job` field every request that runs the work carries. */\nexport interface SurfaceJobRef {\n task_id: string;\n report_token: string;\n expects?: JobExpectation;\n follow?: FollowSpec;\n}\n\nexport interface ServerJob {\n remoteId: string;\n status?: string;\n reportToken?: string;\n}\n\n/** How long past its own run budget a production is followed. */\nexport const PROMO_FOLLOW_SLACK_MS = 15 * 60_000;\n\n/** How the agent follows a production it was handed (web `promoFollowSpec`). */\nexport function promoFollowSpec(opts: Pick<PromoRunOptions, 'preset' | 'quality'>): FollowSpec {\n return {\n connector: PROMO_CONNECTOR,\n action: PRODUCE_STATUS_ACTION,\n params: { projectId: 'projectId', sandboxId: 'sandboxId' },\n interval_ms: PROMO_POLL_EVERY_MS,\n deadline_ms: promoRunTimeoutMs(opts) + PROMO_FOLLOW_SLACK_MS,\n state_key: 'state',\n succeeded: ['succeeded'],\n failed: ['failed'],\n };\n}\n\n/** How the agent follows a lip sync it was handed (web `LIPSYNC_FOLLOW`). */\nexport const LIPSYNC_FOLLOW: FollowSpec = {\n connector: 'yantra-audio',\n action: 'YANTRA_AUDIO_LIPSYNC_STATUS',\n params: { statusUrl: 'statusUrl', responseUrl: 'responseUrl' },\n interval_ms: 5_000,\n deadline_ms: 15 * 60_000,\n state_key: 'status',\n succeeded: ['completed'],\n failed: ['failed'],\n};\n\nexport const isSettledStatus = (status: string | undefined): boolean => status === 'COMPLETED' || status === 'BLOCKED';\n\n// ── Transport ────────────────────────────────────────────────────────────────\n\nexport interface GraphqlResult {\n data?: Record<string, any>;\n errors?: { message?: string }[];\n}\n\nexport interface TaskApiTransport {\n post(query: string, variables?: Record<string, unknown>): Promise<GraphqlResult>;\n}\n\n/** fetch against the backend GraphQL with the surface bearer. */\nexport function createTaskApiTransport(token: string, graphqlUrl: string): TaskApiTransport {\n return {\n async post(query, variables = {}) {\n const resp = await fetch(graphqlUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },\n body: JSON.stringify({ query, variables }),\n });\n const body = (await resp.json().catch(() => ({}))) as GraphqlResult;\n const text = (body.errors ?? []).map((e) => e.message || 'error').join('; ');\n if (!resp.ok) throw new Error(`task api: HTTP ${resp.status}${text ? `: ${text}` : ''}`);\n if (body.errors?.length) throw new Error(`task api: ${text}`);\n return body;\n },\n };\n}\n\nlet transport: TaskApiTransport | null = null;\nlet owner: string | undefined;\n\n/**\n * Point the Task API at this session. CanvasExperience calls it whenever the\n * token or index changes; without a backend the transport is null and every\n * call below answers \"nothing\" rather than throwing.\n */\nexport function configureServerTasks(session: { token?: string | null; indexUrl?: string | null }): void {\n const graphqlUrl = session.indexUrl ? deriveBackendGraphqlUrl(session.indexUrl) : null;\n transport = session.token && graphqlUrl ? createTaskApiTransport(session.token, graphqlUrl) : null;\n const sub = tokenClaims(session.token).sub;\n owner = typeof sub === 'string' && sub ? sub : undefined;\n}\n\n/** Tests only. */\nexport function setTaskApiTransport(next: TaskApiTransport | null): void {\n transport = next;\n}\n\nexport const taskApiTransport = (): TaskApiTransport | null => transport;\n/** The JWT subject of who is signed in, for a job's owner. */\nexport const currentOwner = (): string | undefined => owner;\n\n// ── Capability ───────────────────────────────────────────────────────────────\n\nlet missing = new WeakMap<TaskApiTransport, Set<string>>();\nconst isMissing = (t: TaskApiTransport, field: string): boolean => missing.get(t)?.has(field) === true;\nfunction markMissing(t: TaskApiTransport, field: string): void {\n const set = missing.get(t) ?? new Set<string>();\n set.add(field);\n missing.set(t, set);\n}\nconst isMissingField = (err: unknown, field: string): boolean =>\n new RegExp(`Cannot query field [\"']${field}[\"']`).test(err instanceof Error ? err.message : String(err ?? ''));\n\n/** Ask a field the backend may not have. `undefined` when it does not (now or since). */\nasync function optionalField(\n t: TaskApiTransport,\n field: string,\n query: string,\n variables: Record<string, unknown>,\n): Promise<unknown> {\n if (isMissing(t, field)) return undefined;\n try {\n const res = await t.post(query, variables);\n return res.data?.[field] ?? null;\n } catch (err) {\n if (!isMissingField(err, field)) throw err;\n markMissing(t, field);\n return undefined;\n }\n}\n\n// ── The calls ────────────────────────────────────────────────────────────────\n\nconst ROW = 'id title description status createdAt updatedAt source { type id details }';\n\nconst RECORD = `mutation SurfaceJobRecord($job: JSON!) {\n recordSurfaceJob(job: $job) { id status reportToken }\n}`;\nconst PATCH = `mutation SurfaceJobPatch($id: ID!, $patch: JSON!) {\n updateSurfaceJob(id: $id, patch: $patch)\n}`;\nconst CLAIM = `mutation SurfaceJobClaim($id: ID!) {\n claimSurfaceJobDelivery(id: $id)\n}`;\nconst DELETE = `mutation SurfaceJobDelete($id: ID!) {\n removeSurfaceJob(id: $id)\n}`;\nconst LIST_OWN = `query SurfaceJobsOwn($surface: String, $limit: Int) {\n getSurfaceJobs(surface: $surface, limit: $limit) { ${ROW} }\n}`;\nconst READ_OWN = `query SurfaceJobOne($id: ID!) {\n getSurfaceJob(id: $id) { ${ROW} }\n}`;\n\n/** Record (or find) a job's row. Idempotent: the same local job always lands on the same row. */\nexport async function recordSurfaceJob(t: TaskApiTransport, job: JobRecord): Promise<ServerJob | null> {\n const rec = (await optionalField(t, 'recordSurfaceJob', RECORD, { job: browserDetails(job) })) as\n | {\n id?: unknown;\n status?: unknown;\n reportToken?: unknown;\n }\n | null\n | undefined;\n if (!rec || typeof rec.id !== 'string' || !rec.id) return null;\n return {\n remoteId: rec.id,\n ...(typeof rec.status === 'string' ? { status: rec.status } : {}),\n ...(typeof rec.reportToken === 'string' && rec.reportToken ? { reportToken: rec.reportToken } : {}),\n };\n}\n\nconst PATCH_KEYS = [\n 'progress',\n 'stage',\n 'results',\n 'error',\n 'delivered',\n 'state',\n 'startedAt',\n 'endedAt',\n 'title',\n 'brief',\n 'input',\n 'handle',\n 'deadlineAt',\n] as const;\n\n/** What an update sends: the fields this job HAS, merged into the row field by field. */\nexport function surfaceJobPatch(job: JobRecord): Record<string, unknown> {\n const wire = browserDetails(job);\n const patch: Record<string, unknown> = {};\n for (const key of PATCH_KEYS) if (wire[key] !== undefined) patch[key] = wire[key];\n return patch;\n}\n\n/** Merge a patch into a row. The server ignores a state change once the agent has given its verdict. */\nexport async function updateSurfaceJob(\n t: TaskApiTransport,\n remoteId: string,\n patch: Record<string, unknown>,\n): Promise<boolean> {\n return (await optionalField(t, 'updateSurfaceJob', PATCH, { id: remoteId, patch })) === true;\n}\n\n/**\n * Claim the right to place a finished job's result: true for exactly one\n * caller however many devices have the board open. Undefined when the backend\n * predates claims - the caller then places on its own record's word.\n */\nexport async function claimDelivery(t: TaskApiTransport, remoteId: string): Promise<boolean | undefined> {\n const value = await optionalField(t, 'claimSurfaceJobDelivery', CLAIM, { id: remoteId });\n return value === undefined ? undefined : value === true;\n}\n\nexport async function removeSurfaceJob(t: TaskApiTransport, remoteId: string): Promise<boolean> {\n return (await optionalField(t, 'removeSurfaceJob', DELETE, { id: remoteId })) === true;\n}\n\nfunction rowsOf(value: unknown): RemoteTaskRow[] {\n if (Array.isArray(value)) return value as RemoteTaskRow[];\n const data = (value as { data?: unknown } | null)?.data;\n return Array.isArray(data) ? (data as RemoteTaskRow[]) : [];\n}\n\n/** The caller's own jobs for one surface, newest first. Empty against a backend without them. */\nexport async function listRemoteJobs(t: TaskApiTransport, surface = CANVAS_SURFACE, limit = 100): Promise<JobRecord[]> {\n const own = await optionalField(t, 'getSurfaceJobs', LIST_OWN, { surface, limit });\n if (own === undefined) return [];\n const jobs: JobRecord[] = [];\n for (const row of rowsOf(own)) {\n const job = jobFromTaskRow(row);\n if (job && job.surface === surface) jobs.push(job);\n }\n return jobs;\n}\n\n/** One of the caller's jobs; null when gone or not theirs, undefined when the backend cannot read one row. */\nexport async function getRemoteJob(t: TaskApiTransport, remoteId: string): Promise<JobRecord | null | undefined> {\n const row = await optionalField(t, 'getSurfaceJob', READ_OWN, { id: remoteId });\n if (row === undefined) return undefined;\n return jobFromTaskRow(row as RemoteTaskRow | null);\n}\n\n// ── Recording before the submit ──────────────────────────────────────────────\n\ninterface HeldJob {\n remoteId: string;\n reportToken?: string;\n}\n\nconst pendingJobs = new Map<string, Promise<ServerJob | null>>();\nconst heldJobs = new Map<string, HeldJob>();\n\n/** Record a job before its work is submitted; null after the budget, or without a backend. */\nexport async function recordServerJob(\n t: TaskApiTransport,\n job: JobRecord,\n budgetMs = SERVER_JOB_BUDGET_MS,\n): Promise<ServerJob | null> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const outOfTime = new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), budgetMs);\n });\n try {\n return await Promise.race([recordSurfaceJob(t, job).catch(() => null), outOfTime]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Record a local job on the server and hold its report token for\n * {@link serverJobRef}. `exists` is asked once the answer is in: a record\n * dismissed meanwhile takes its row down rather than leaving an orphan.\n */\nexport function recordLocalJob(\n job: JobRecord,\n options: { exists?: () => boolean; budgetMs?: number; transport?: TaskApiTransport | null } = {},\n): Promise<ServerJob | null> {\n const t = options.transport === undefined ? transport : options.transport;\n const key = jobKey(job);\n if (!t) return Promise.resolve(null);\n const recorded = recordServerJob(t, job, options.budgetMs)\n .then((server) => {\n if (!server) return null;\n if (options.exists && !options.exists()) {\n void removeSurfaceJob(t, server.remoteId).catch(() => undefined);\n return null;\n }\n heldJobs.set(key, {\n remoteId: server.remoteId,\n // A row already settled keeps its verdict: no request may hand it out again.\n ...(server.reportToken && !isSettledStatus(server.status) ? { reportToken: server.reportToken } : {}),\n });\n return server;\n })\n .catch(() => null);\n pendingJobs.set(key, recorded);\n return recorded;\n}\n\n/**\n * The `job` field for the request whose outcome IS this job's outcome, or\n * undefined to send none. Waits for the job's recording (its budget at most).\n */\nexport async function serverJobRef(\n key: string,\n extras: { expects?: JobExpectation; follow?: FollowSpec } = {},\n): Promise<SurfaceJobRef | undefined> {\n await pendingJobs.get(key);\n const held = heldJobs.get(key);\n if (!held?.reportToken) return undefined;\n return {\n task_id: held.remoteId,\n report_token: held.reportToken,\n ...(extras.expects ? { expects: extras.expects } : {}),\n ...(extras.follow ? { follow: extras.follow } : {}),\n };\n}\n\n/** A canvas job's server id from the listing - for a run whose token a restart dropped. */\nexport async function lookupRemoteJobId(localId: string): Promise<string | undefined> {\n const t = transport;\n if (!t) return undefined;\n const jobs = await listRemoteJobs(t, CANVAS_SURFACE);\n return jobs.find((job) => job.id === localId)?.remoteId;\n}\n\n/** The server id a recorded job landed on, once it has. */\nexport const serverJobId = (key: string): string | undefined => heldJobs.get(key)?.remoteId;\n\n/** Drop a settled job's token. */\nexport function forgetServerJob(key: string): void {\n pendingJobs.delete(key);\n heldJobs.delete(key);\n}\n\n/** Tests only. */\nexport function resetServerTasks(): void {\n pendingJobs.clear();\n heldJobs.clear();\n missing = new WeakMap();\n transport = null;\n owner = undefined;\n}\n\n// ── Running the work with the job ────────────────────────────────────────────\n\n/**\n * Run a connector action carrying `job` when the runner can put it on the exec\n * body. Answers whether the job actually went out - a host seam that predates\n * the field cannot carry it, and then nobody will report on the job.\n */\nexport async function runWithJob(\n run: RunMediaAction,\n connector: MediaConnector,\n action: string,\n params: Record<string, unknown>,\n job: SurfaceJobRef | undefined,\n): Promise<{ result: Record<string, unknown>; handed: boolean }> {\n if (job && runActionAcceptsJob(run)) {\n return { result: await (run as RunMediaActionWithBody)(connector, action, params, { job }), handed: true };\n }\n return { result: await run(connector, action, params), handed: false };\n}\n\n// ── Waiting on the agent ─────────────────────────────────────────────────────\n\nexport const AGENT_VERDICT_INTERVAL_MS = 5_000;\nconst MAX_READ_MISSES = 6;\n\nexport interface AwaitAgentVerdictOptions {\n submittedAt: number;\n deadlineAt: number;\n intervalMs?: number;\n graceMs?: number;\n onProgress?: (job: JobRecord) => void;\n sleep?: (ms: number) => Promise<void>;\n now?: () => number;\n transport?: TaskApiTransport | null;\n}\n\n/**\n * Wait for the agent's verdict on one job by reading its row - for a phone\n * that is open while the agent follows the work, and so must NOT poll it too\n * (a promo sandbox dies on the first terminal STATUS answer). Resolves the row\n * once terminal; null when the agent never reported within the grace or the\n * row cannot be read - the caller then follows the work itself.\n */\nexport async function awaitAgentVerdict(\n remoteId: string,\n options: AwaitAgentVerdictOptions,\n): Promise<JobRecord | null> {\n const t = options.transport === undefined ? transport : options.transport;\n if (!t) return null;\n const {\n submittedAt,\n deadlineAt,\n intervalMs = AGENT_VERDICT_INTERVAL_MS,\n graceMs = AGENT_OWNED_GRACE_MS,\n onProgress,\n sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n now = () => Date.now(),\n } = options;\n let misses = 0;\n for (;;) {\n await sleep(intervalMs);\n let job: JobRecord | null | undefined;\n try {\n job = await getRemoteJob(t, remoteId);\n misses = 0;\n } catch {\n if (++misses >= MAX_READ_MISSES) return null;\n continue;\n }\n if (!job) return null;\n if (job.state !== 'running') return job;\n const at = now();\n if (job.agentOwned) onProgress?.(job);\n else if (at - submittedAt >= graceMs) return null;\n if (at >= deadlineAt) throw new Error(DEADLINE_VERDICT_ERROR);\n }\n}\n\n/** A row's latest line: its stage, else its progress. */\nexport function serverProgressLine(job: Pick<JobRecord, 'stage' | 'progress'>): string | undefined {\n if (job.stage) {\n const step = `Step ${job.stage.index} of ${job.stage.total}`;\n return job.stage.detail ? `${step} · ${job.stage.detail}` : `${step} · ${job.stage.label}`;\n }\n return job.progress;\n}\n\n// ── Promo ────────────────────────────────────────────────────────────────────\n\n/** The production's video from its verdict row: its results, else the STATUS answer the agent kept. */\nexport function promoVideoFromVerdict(\n job: Pick<JobRecord, 'results' | 'result'>,\n): { url: string; title?: string } | undefined {\n const video = job.results?.find((r) => r.kind === 'video' && r.url) ?? job.results?.find((r) => r.url);\n if (video) return { url: video.url, ...(video.title ? { title: video.title } : {}) };\n const answer = (job.result ?? {}) as Record<string, unknown>;\n const inner = (answer.data && typeof answer.data === 'object' ? answer.data : answer) as Record<string, unknown>;\n const nested = (inner.result && typeof inner.result === 'object' ? inner.result : inner) as Record<string, unknown>;\n const url = str(nested.url) ?? str(nested.videoUrl) ?? str(nested.video_url);\n return url ? { url, ...(str(nested.title) ? { title: str(nested.title) } : {}) } : undefined;\n}\n\n/**\n * Record a production's job and hand back the ref its SUBMIT carries (with\n * the follow spec). Undefined when there is no backend, an older one, or it\n * did not answer in two seconds.\n */\nexport async function registerPromoJob(task: CanvasTask, exists: () => boolean): Promise<SurfaceJobRef | undefined> {\n const job = canvasTaskToJob(task);\n const withOwner = owner ? { ...job, owner } : job;\n await recordLocalJob(withOwner, { exists });\n return serverJobRef(canvasJobKey(task.id), { follow: promoFollowSpec(task.request) });\n}\n\n// ── Lip sync ─────────────────────────────────────────────────────────────────\n\n/** The synced clip in an agent's verdict: its results, else the STATUS answer it kept. */\nexport function lipSyncVideoFromVerdict(verdict: Pick<JobRecord, 'results' | 'result'>): string {\n const fromResults = verdict.results?.find((r) => r.kind === 'video' && r.url)?.url;\n if (fromResults) return fromResults;\n const answer = (verdict.result ?? {}) as Record<string, unknown>;\n const inner = (answer.data && typeof answer.data === 'object' ? answer.data : answer) as Record<string, unknown>;\n return typeof inner.videoUrl === 'string' ? inner.videoUrl : '';\n}\n\nlet lipSeq = 0;\n\nexport interface TrackedLipSync {\n /** The job's id (the local half of its key). */\n id: string;\n /** The `job` for the SUBMIT, with {@link LIPSYNC_FOLLOW}; undefined when there is no record. */\n job?: SurfaceJobRef;\n /** Wait on the agent's verdict instead of polling the queue beside it; null when the agent never took it. */\n awaitAgent: (startedAt: number) => Promise<JobRecord | null>;\n /** Settle the row with the phone's own verdict (when the phone followed the queue itself). */\n settle: (outcome: { videoUrl?: string; error?: string }) => void;\n}\n\n/**\n * Record a lip sync as a canvas job so it shows in every Tasks panel and the\n * agent can finish it if the phone goes away. Never throws.\n */\nexport async function trackLipSync(input: {\n videoUrl: string;\n text?: string;\n boardId?: string | null;\n}): Promise<TrackedLipSync> {\n const id = `lipsync-${Date.now().toString(36)}-${(lipSeq++).toString(36)}`;\n const createdAt = Date.now();\n const base: JobRecord = {\n id,\n surface: CANVAS_SURFACE,\n lane: LIPSYNC_LANE,\n title: 'Lip sync',\n brief: input.text?.trim() || 'Lip sync',\n state: 'running',\n createdAt,\n startedAt: createdAt,\n deadlineAt: jobDeadlineAt(createdAt, LIPSYNC_BUDGET_MS),\n input: {\n ...(input.text ? { prompt: input.text } : {}),\n connector: 'yantra-audio',\n action: 'YANTRA_AUDIO_LIPSYNC_SUBMIT',\n params: { videoUrl: input.videoUrl, ...(input.text ? { text: input.text } : {}) },\n },\n ...(input.boardId ? { handle: { boardId: input.boardId } } : {}),\n ...(owner ? { owner } : {}),\n };\n const key = jobKey(base);\n const server = await recordLocalJob(base).catch(() => null);\n const job = server ? await serverJobRef(key, { follow: LIPSYNC_FOLLOW, expects: 'media' }) : undefined;\n return {\n id,\n ...(job ? { job } : {}),\n awaitAgent: (startedAt) =>\n server\n ? awaitAgentVerdict(server.remoteId, { submittedAt: startedAt, deadlineAt: base.deadlineAt! })\n : Promise.resolve(null),\n settle: ({ videoUrl, error }) => {\n const t = transport;\n forgetServerJob(key);\n if (!server || !t) return;\n const done: JobRecord = {\n ...base,\n state: videoUrl ? 'succeeded' : 'failed',\n endedAt: Date.now(),\n ...(videoUrl ? { results: [{ url: videoUrl, kind: 'video' as const }], delivered: true } : {}),\n ...(error ? { error } : {}),\n };\n void updateSurfaceJob(t, server.remoteId, surfaceJobPatch(done)).catch(() => undefined);\n },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {jsx}from'react/jsx-runtime';import {useRef,useMemo}from'react';import {StyleSheet,View}from'react-native';import {useWebViewRetention}from'../../../hooks/useWebViewRetention.js';function loadWebView() {
1
+ import {jsx}from'react/jsx-runtime';import {useRef,useMemo,useEffect}from'react';import {StyleSheet,View}from'react-native';import {DEFAULT_LIGHTING,MESH_TARGET_HEIGHT_M}from'./setProject.js';import {KIT_ENTRIES}from'./supplyLibrary.js';import {useWebViewRetention}from'../../../hooks/useWebViewRetention.js';function loadWebView() {
2
2
  try {
3
3
  return require("react-native-webview").WebView;
4
4
  } catch (e) {
@@ -8,6 +8,13 @@ import {jsx}from'react/jsx-runtime';import {useRef,useMemo}from'react';import {S
8
8
  function esc(value) {
9
9
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
10
10
  }
11
+ const KIT_FOR_PAGE = JSON.stringify(Object.fromEntries(KIT_ENTRIES.map((entry) => [entry.id, {
12
+ parts: entry.parts,
13
+ color: entry.color
14
+ }]))).replace(/</g, "\\u003c");
15
+ function viewportMessageScript(msg) {
16
+ return `window.dispatchEvent(new MessageEvent('message',{data:${JSON.stringify(JSON.stringify(msg))}}));true;`;
17
+ }
11
18
  function setViewportHtml(opts) {
12
19
  const world = opts.worldUrl ? esc(opts.worldUrl) : "";
13
20
  const prop = opts.meshOrImageUrl ? esc(opts.meshOrImageUrl) : "";
@@ -22,9 +29,12 @@ import * as THREE from 'three';
22
29
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
23
30
  const worldUrl=${JSON.stringify(world)};
24
31
  const propUrl=${JSON.stringify(prop)};
32
+ const KIT=${KIT_FOR_PAGE};
33
+ const MESH_H=${MESH_TARGET_HEIGHT_M};
34
+ const D2R=Math.PI/180;
25
35
  const scene=new THREE.Scene();
26
36
  scene.background=new THREE.Color(0x1a1a1e);
27
- const camera=new THREE.PerspectiveCamera(${cam.fov},1,0.1,200);
37
+ const camera=new THREE.PerspectiveCamera(${cam.fov},1,0.05,200);
28
38
  camera.position.set(${cam.position.x},${cam.position.y},${cam.position.z});
29
39
  /* preserveDrawingBuffer stays FALSE: it doubles the framebuffer and stops the
30
40
  driver discarding it after compositing. capture() re-renders synchronously
@@ -36,41 +46,132 @@ const controls=new OrbitControls(camera,renderer.domElement);
36
46
  controls.target.set(${cam.target.x},${cam.target.y},${cam.target.z});
37
47
  controls.enableDamping=true;
38
48
  const light=new THREE.DirectionalLight(0xffffff,1.1);
39
- light.position.set(2,4,3);scene.add(light);scene.add(new THREE.AmbientLight(0xffffff,0.45));
49
+ light.position.set(2,4,3);scene.add(light);
50
+ const ambient=new THREE.AmbientLight(0xffffff,0.45);scene.add(ambient);
40
51
  const ground=new THREE.Mesh(new THREE.CircleGeometry(4,48),new THREE.MeshStandardMaterial({color:0x2a2a30,roughness:0.9}));
41
52
  ground.rotation.x=-Math.PI/2;scene.add(ground);
42
53
  function fit(){const w=innerWidth,h=innerHeight;camera.aspect=w/Math.max(1,h);camera.updateProjectionMatrix();renderer.setSize(w,h,false);}
43
54
  addEventListener('resize',fit);fit();
44
55
  function post(type,payload){window.ReactNativeWebView&&window.ReactNativeWebView.postMessage(JSON.stringify({type,...payload}));}
45
- if(worldUrl){new THREE.TextureLoader().load(worldUrl,tex=>{tex.mapping=THREE.EquirectangularReflectionMapping;scene.background=tex;scene.environment=tex;});}
56
+ /* A generated model or world that will not load must say so, not draw nothing. */
57
+ function failed(what){return ()=>post('loadError',{what});}
58
+ if(worldUrl){new THREE.TextureLoader().load(worldUrl,tex=>{tex.mapping=THREE.EquirectangularReflectionMapping;scene.background=tex;scene.environment=tex;},undefined,failed('world'));}
59
+ let gltfLoader=null;
60
+ function loadGlb(url,ok,bad){
61
+ (gltfLoader?Promise.resolve(gltfLoader):import('three/addons/loaders/GLTFLoader.js').then(({GLTFLoader})=>(gltfLoader=new GLTFLoader())))
62
+ .then(l=>l.load(url,ok,undefined,bad)).catch(bad);
63
+ }
46
64
  if(propUrl){
47
65
  if(/\\.glb($|\\?)/i.test(propUrl)){
48
- import('three/addons/loaders/GLTFLoader.js').then(({GLTFLoader})=>{
49
- new GLTFLoader().load(propUrl,g=>{const root=g.scene;root.position.set(0,0,0);scene.add(root);});
50
- });
66
+ loadGlb(propUrl,g=>{const root=g.scene;root.position.set(0,0,0);scene.add(root);},failed('object'));
51
67
  } else {
52
68
  new THREE.TextureLoader().load(propUrl,tex=>{
53
69
  const aspect=tex.image.width/Math.max(1,tex.image.height);
54
70
  const w=1.2,h=w/aspect;
55
71
  const mesh=new THREE.Mesh(new THREE.PlaneGeometry(w,h),new THREE.MeshBasicMaterial({map:tex,transparent:true}));
56
72
  mesh.position.set(0,h/2,0);scene.add(mesh);
57
- });
73
+ },undefined,failed('object'));
74
+ }
75
+ }
76
+ /* \u2500\u2500 The set's objects \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
77
+ const live=new Map();
78
+ function partGeometry(p){
79
+ switch(p.shape){
80
+ case 'box':return new THREE.BoxGeometry(p.a,p.b??p.a,p.c??p.a);
81
+ case 'sphere':return new THREE.SphereGeometry(p.a,24,16);
82
+ case 'cylinder':return new THREE.CylinderGeometry(p.a,p.a,p.b??1,24);
83
+ case 'cone':return new THREE.ConeGeometry(p.a,p.b??1,24);
84
+ case 'torus':return new THREE.TorusGeometry(p.a,p.b??0.1,12,32);
85
+ case 'capsule':return new THREE.CapsuleGeometry(p.a,p.b??0.5,6,12);
86
+ case 'plane':{const g=new THREE.PlaneGeometry(p.a,p.b??p.a);g.rotateX(-Math.PI/2);g.translate(0,0.003,0);return g;}
87
+ default:return new THREE.BoxGeometry(0.3,0.3,0.3);
88
+ }
89
+ }
90
+ function placeholder(holder,why){
91
+ const box=new THREE.Mesh(new THREE.BoxGeometry(0.4,0.4,0.4),new THREE.MeshBasicMaterial({color:0xd97b3f,wireframe:true}));
92
+ box.position.y=0.2;holder.add(box);
93
+ if(why)post('objectError',{id:holder.userData.id,why});
94
+ }
95
+ function buildKit(o,holder){
96
+ const e=KIT[o.kitId];
97
+ if(!e){placeholder(holder,'unknown kit '+o.kitId);return;}
98
+ for(const p of e.parts){
99
+ const m=new THREE.Mesh(partGeometry(p),new THREE.MeshStandardMaterial({color:p.color||o.color||e.color,roughness:0.8}));
100
+ if(p.offset)m.position.set(p.offset[0],p.offset[1],p.offset[2]);
101
+ if(p.rotate)m.rotation.set(p.rotate[0]*D2R,p.rotate[1]*D2R,p.rotate[2]*D2R);
102
+ holder.add(m);
58
103
  }
59
104
  }
105
+ function buildMesh(o,holder){
106
+ if(!o.modelUrl){placeholder(holder,'no model');return;}
107
+ loadGlb(o.modelUrl,g=>{
108
+ const root=g.scene;
109
+ const box=new THREE.Box3().setFromObject(root);const size=box.getSize(new THREE.Vector3());
110
+ const k=size.y>0?MESH_H/size.y:1;root.scale.setScalar(k);
111
+ const c=box.getCenter(new THREE.Vector3());root.position.set(-c.x*k,-box.min.y*k,-c.z*k);
112
+ holder.add(root);
113
+ },()=>{placeholder(holder,'the model would not load');post('loadError',{what:'object'});});
114
+ }
115
+ function buildImage(o,holder){
116
+ if(!o.imageUrl){placeholder(holder,'no image');return;}
117
+ new THREE.TextureLoader().load(o.imageUrl,tex=>{
118
+ const w=o.imageWidthM||1,h=w*tex.image.height/Math.max(1,tex.image.width);
119
+ const m=new THREE.Mesh(new THREE.PlaneGeometry(w,h),new THREE.MeshBasicMaterial({map:tex,transparent:true,side:THREE.DoubleSide}));
120
+ m.position.y=h/2;holder.add(m);
121
+ },undefined,()=>{placeholder(holder,'the image would not load');post('loadError',{what:'object'});});
122
+ }
123
+ const sigOf=o=>[o.source,o.kitId||o.modelUrl||o.imageUrl||'',o.color||'',o.imageWidthM||''].join('|');
124
+ function place(holder,o){
125
+ if(o.position)holder.position.set(o.position.x,o.position.y,o.position.z);
126
+ if(o.rotation)holder.rotation.set(o.rotation.x*D2R,o.rotation.y*D2R,o.rotation.z*D2R);
127
+ if(o.scale)holder.scale.set(o.scale.x,o.scale.y,o.scale.z);
128
+ if(typeof o.hidden==='boolean')holder.visible=!o.hidden;
129
+ }
130
+ function addObject(o){
131
+ const prev=live.get(o.id);
132
+ if(prev&&prev.sig===sigOf(o)){place(prev.holder,o);return;}
133
+ if(prev)scene.remove(prev.holder);
134
+ const holder=new THREE.Group();holder.userData.id=o.id;
135
+ if(o.source==='kit')buildKit(o,holder);else if(o.source==='mesh')buildMesh(o,holder);else buildImage(o,holder);
136
+ place(holder,o);scene.add(holder);live.set(o.id,{holder,sig:sigOf(o)});
137
+ }
138
+ function removeObject(id){const rec=live.get(id);if(rec){scene.remove(rec.holder);live.delete(id);}}
139
+ function syncObjects(list){
140
+ const keep=new Set(list.map(o=>o.id));
141
+ for(const id of [...live.keys()])if(!keep.has(id))removeObject(id);
142
+ list.forEach(addObject);
143
+ }
144
+ function setLight(l){
145
+ const az=l.keyAzimuthDeg*D2R,el=l.keyElevationDeg*D2R;
146
+ light.position.set(Math.sin(az)*Math.cos(el)*6,Math.sin(el)*6,Math.cos(az)*Math.cos(el)*6);
147
+ light.intensity=l.keyIntensity*0.42;ambient.intensity=l.ambientIntensity*0.82;
148
+ }
149
+ function frameObject(id){
150
+ const rec=live.get(id);if(!rec)return;
151
+ const box=new THREE.Box3().setFromObject(rec.holder);if(box.isEmpty())return;
152
+ const c=box.getCenter(new THREE.Vector3());const r=Math.max(0.15,box.getSize(new THREE.Vector3()).length()/2);
153
+ const dir=camera.position.clone().sub(controls.target).normalize();
154
+ controls.target.copy(c);camera.position.copy(c.clone().add(dir.multiplyScalar(r*3)));controls.update();
155
+ }
60
156
  function tick(){controls.update();renderer.render(scene,camera);requestAnimationFrame(tick);}tick();
61
157
  function capture(){renderer.render(scene,camera);return renderer.domElement.toDataURL('image/jpeg',0.92);}
62
- addEventListener('message',(ev)=>{
158
+ function onMessage(ev){
63
159
  let msg={};try{msg=JSON.parse(ev.data);}catch(e){return;}
64
160
  if(msg.type==='capture'){post('frame',{dataUrl:capture()});}
65
161
  if(msg.type==='setCamera'&&msg.camera){
66
162
  const c=msg.camera;camera.position.set(c.position.x,c.position.y,c.position.z);
163
+ if(c.fov){camera.fov=c.fov;camera.updateProjectionMatrix();}
67
164
  controls.target.set(c.target.x,c.target.y,c.target.z);controls.update();
68
165
  }
69
- });
70
- document.addEventListener('message',(ev)=>{
71
- let msg={};try{msg=JSON.parse(ev.data);}catch(e){return;}
72
- if(msg.type==='capture'){post('frame',{dataUrl:capture()});}
73
- });
166
+ if(msg.type==='objects'&&Array.isArray(msg.objects))syncObjects(msg.objects);
167
+ if(msg.type==='addObject'&&msg.object)addObject(msg.object);
168
+ if(msg.type==='moveObject'&&msg.object){const rec=live.get(msg.object.id);if(rec)place(rec.holder,msg.object);}
169
+ if(msg.type==='removeObject'&&msg.id)removeObject(msg.id);
170
+ if(msg.type==='setLight'&&msg.lighting)setLight(msg.lighting);
171
+ if(msg.type==='frameObject'&&msg.id)frameObject(msg.id);
172
+ }
173
+ addEventListener('message',onMessage);
174
+ document.addEventListener('message',onMessage);
74
175
  post('ready',{});
75
176
  <\/script></body></html>`;
76
177
  }
@@ -78,43 +179,121 @@ function SetViewportWebView({
78
179
  worldUrl,
79
180
  propUrl,
80
181
  camera,
182
+ objects,
183
+ lighting,
81
184
  onFrame,
185
+ onLoadError,
82
186
  webRef
83
187
  }) {
84
188
  const WebView = loadWebView();
85
189
  const ref = useRef(null);
190
+ const cameraRef = useRef(camera);
191
+ cameraRef.current = camera;
192
+ const objectsRef = useRef(objects);
193
+ objectsRef.current = objects;
194
+ const lightingRef = useRef(lighting);
195
+ lightingRef.current = lighting;
86
196
  const html = useMemo(() => setViewportHtml({
87
197
  worldUrl,
88
198
  meshOrImageUrl: propUrl,
89
- camera
90
- }), [worldUrl, propUrl, camera]);
199
+ camera: cameraRef.current
200
+ }), [worldUrl, propUrl]);
91
201
  const shouldRender = useWebViewRetention({
92
202
  releaseOnBlur: true
93
203
  });
204
+ const send = (msg) => {
205
+ var _a, _b;
206
+ return (_b = (_a = ref.current) == null ? void 0 : _a.injectJavaScript) == null ? void 0 : _b.call(_a, viewportMessageScript(msg));
207
+ };
94
208
  if (webRef) {
95
209
  webRef.current = {
96
- capture: () => {
97
- var _a, _b;
98
- (_b = (_a = ref.current) == null ? void 0 : _a.injectJavaScript) == null ? void 0 : _b.call(_a, `window.dispatchEvent(new MessageEvent('message',{data:JSON.stringify({type:'capture'})}));true;`);
99
- },
100
- setCamera: (c) => {
101
- var _a, _b;
102
- (_b = (_a = ref.current) == null ? void 0 : _a.injectJavaScript) == null ? void 0 : _b.call(_a, `window.dispatchEvent(new MessageEvent('message',{data:${JSON.stringify(JSON.stringify({
103
- type: "setCamera",
104
- camera: c
105
- }))}}));true;`);
106
- }
210
+ capture: () => send({
211
+ type: "capture"
212
+ }),
213
+ setCamera: (c) => send({
214
+ type: "setCamera",
215
+ camera: c
216
+ }),
217
+ addObject: (object) => send({
218
+ type: "addObject",
219
+ object
220
+ }),
221
+ moveObject: (object) => send({
222
+ type: "moveObject",
223
+ object: {
224
+ id: object.id,
225
+ position: object.position,
226
+ rotation: object.rotation,
227
+ scale: object.scale,
228
+ hidden: Boolean(object.hidden)
229
+ }
230
+ }),
231
+ removeObject: (id) => send({
232
+ type: "removeObject",
233
+ id
234
+ }),
235
+ syncObjects: (list) => send({
236
+ type: "objects",
237
+ objects: list
238
+ }),
239
+ setLight: (l) => send({
240
+ type: "setLight",
241
+ lighting: l
242
+ }),
243
+ frameObject: (id) => send({
244
+ type: "frameObject",
245
+ id
246
+ })
107
247
  };
108
248
  }
249
+ useEffect(() => {
250
+ if (objects) send({
251
+ type: "objects",
252
+ objects
253
+ });
254
+ }, [objects]);
255
+ useEffect(() => {
256
+ if (lighting) send({
257
+ type: "setLight",
258
+ lighting
259
+ });
260
+ }, [lighting]);
261
+ const firstCamera = useRef(true);
262
+ useEffect(() => {
263
+ if (firstCamera.current) {
264
+ firstCamera.current = false;
265
+ return;
266
+ }
267
+ send({
268
+ type: "setCamera",
269
+ camera
270
+ });
271
+ }, [camera]);
109
272
  if (!WebView || !shouldRender) return /* @__PURE__ */ jsx(View, { style: styles.fallback });
110
273
  return /* @__PURE__ */ jsx(WebView, { ref: (node) => {
111
274
  ref.current = node;
112
275
  }, originWhitelist: ["*"], source: {
113
276
  html
114
277
  }, style: styles.view, onMessage: (event) => {
278
+ var _a, _b;
115
279
  try {
116
280
  const msg = JSON.parse(event.nativeEvent.data);
117
281
  if (msg.type === "frame" && msg.dataUrl) onFrame == null ? void 0 : onFrame(msg.dataUrl);
282
+ if (msg.type === "loadError") onLoadError == null ? void 0 : onLoadError(msg.what === "world" ? "world" : "object");
283
+ if (msg.type === "ready") {
284
+ send({
285
+ type: "setLight",
286
+ lighting: (_a = lightingRef.current) != null ? _a : DEFAULT_LIGHTING
287
+ });
288
+ if ((_b = objectsRef.current) == null ? void 0 : _b.length) send({
289
+ type: "objects",
290
+ objects: objectsRef.current
291
+ });
292
+ send({
293
+ type: "setCamera",
294
+ camera: cameraRef.current
295
+ });
296
+ }
118
297
  } catch (e) {
119
298
  }
120
299
  }, allowFileAccess: true, mixedContentMode: "always" });
@@ -130,4 +309,4 @@ const styles = StyleSheet.create({
130
309
  minHeight: 220,
131
310
  backgroundColor: "#1F1F24"
132
311
  }
133
- });export{SetViewportWebView};//# sourceMappingURL=SetViewportWebView.js.map
312
+ });export{SetViewportWebView,viewportMessageScript};//# sourceMappingURL=SetViewportWebView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SetViewportWebView.js","sources":["../../../../src/features/canvas/set/SetViewportWebView.tsx"],"sourcesContent":["/**\n * Three.js set viewport in a WebView — orbit + still capture for native Set studio.\n * Full desktop SetEditor gizmos stay on web; this ships the interactive viewport.\n */\nimport React, { useMemo, useRef } from 'react';\nimport { StyleSheet, View } from 'react-native';\nimport type { SetCamera } from '../set/setProject';\nimport { useWebViewRetention } from '../../../hooks/useWebViewRetention';\n\nfunction loadWebView(): React.ComponentType<Record<string, unknown>> | null {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('react-native-webview').WebView as React.ComponentType<Record<string, unknown>>;\n } catch {\n return null;\n }\n}\n\nfunction esc(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;');\n}\n\nfunction setViewportHtml(opts: { worldUrl?: string; meshOrImageUrl?: string; camera: SetCamera }): string {\n const world = opts.worldUrl ? esc(opts.worldUrl) : '';\n const prop = opts.meshOrImageUrl ? esc(opts.meshOrImageUrl) : '';\n const cam = opts.camera;\n return `<!DOCTYPE html><html><head>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<style>html,body{margin:0;height:100%;background:#0b0b0e;overflow:hidden}canvas{display:block;width:100%;height:100%}</style>\n</head><body>\n<script type=\"importmap\">{\"imports\":{\"three\":\"https://unpkg.com/three@0.160.0/build/three.module.js\",\"three/addons/\":\"https://unpkg.com/three@0.160.0/examples/jsm/\"}}</script>\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\nconst worldUrl=${JSON.stringify(world)};\nconst propUrl=${JSON.stringify(prop)};\nconst scene=new THREE.Scene();\nscene.background=new THREE.Color(0x1a1a1e);\nconst camera=new THREE.PerspectiveCamera(${cam.fov},1,0.1,200);\ncamera.position.set(${cam.position.x},${cam.position.y},${cam.position.z});\n/* preserveDrawingBuffer stays FALSE: it doubles the framebuffer and stops the\n driver discarding it after compositing. capture() re-renders synchronously\n immediately before toDataURL(), which reads valid pixels without it. */\nconst renderer=new THREE.WebGLRenderer({antialias:true,preserveDrawingBuffer:false});\nrenderer.setPixelRatio(Math.min(devicePixelRatio,2));\ndocument.body.appendChild(renderer.domElement);\nconst controls=new OrbitControls(camera,renderer.domElement);\ncontrols.target.set(${cam.target.x},${cam.target.y},${cam.target.z});\ncontrols.enableDamping=true;\nconst light=new THREE.DirectionalLight(0xffffff,1.1);\nlight.position.set(2,4,3);scene.add(light);scene.add(new THREE.AmbientLight(0xffffff,0.45));\nconst ground=new THREE.Mesh(new THREE.CircleGeometry(4,48),new THREE.MeshStandardMaterial({color:0x2a2a30,roughness:0.9}));\nground.rotation.x=-Math.PI/2;scene.add(ground);\nfunction fit(){const w=innerWidth,h=innerHeight;camera.aspect=w/Math.max(1,h);camera.updateProjectionMatrix();renderer.setSize(w,h,false);}\naddEventListener('resize',fit);fit();\nfunction post(type,payload){window.ReactNativeWebView&&window.ReactNativeWebView.postMessage(JSON.stringify({type,...payload}));}\nif(worldUrl){new THREE.TextureLoader().load(worldUrl,tex=>{tex.mapping=THREE.EquirectangularReflectionMapping;scene.background=tex;scene.environment=tex;});}\nif(propUrl){\n if(/\\\\.glb($|\\\\?)/i.test(propUrl)){\n import('three/addons/loaders/GLTFLoader.js').then(({GLTFLoader})=>{\n new GLTFLoader().load(propUrl,g=>{const root=g.scene;root.position.set(0,0,0);scene.add(root);});\n });\n } else {\n new THREE.TextureLoader().load(propUrl,tex=>{\n const aspect=tex.image.width/Math.max(1,tex.image.height);\n const w=1.2,h=w/aspect;\n const mesh=new THREE.Mesh(new THREE.PlaneGeometry(w,h),new THREE.MeshBasicMaterial({map:tex,transparent:true}));\n mesh.position.set(0,h/2,0);scene.add(mesh);\n });\n }\n}\nfunction tick(){controls.update();renderer.render(scene,camera);requestAnimationFrame(tick);}tick();\nfunction capture(){renderer.render(scene,camera);return renderer.domElement.toDataURL('image/jpeg',0.92);}\naddEventListener('message',(ev)=>{\n let msg={};try{msg=JSON.parse(ev.data);}catch(e){return;}\n if(msg.type==='capture'){post('frame',{dataUrl:capture()});}\n if(msg.type==='setCamera'&&msg.camera){\n const c=msg.camera;camera.position.set(c.position.x,c.position.y,c.position.z);\n controls.target.set(c.target.x,c.target.y,c.target.z);controls.update();\n }\n});\ndocument.addEventListener('message',(ev)=>{\n let msg={};try{msg=JSON.parse(ev.data);}catch(e){return;}\n if(msg.type==='capture'){post('frame',{dataUrl:capture()});}\n});\npost('ready',{});\n</script></body></html>`;\n}\n\nexport function SetViewportWebView({\n worldUrl,\n propUrl,\n camera,\n onFrame,\n webRef,\n}: {\n worldUrl?: string;\n propUrl?: string;\n camera: SetCamera;\n onFrame?: (dataUrl: string) => void;\n webRef?: React.MutableRefObject<{ capture: () => void; setCamera: (c: SetCamera) => void } | null>;\n}) {\n const WebView = loadWebView();\n const ref = useRef<{ injectJavaScript?: (js: string) => void } | null>(null);\n const html = useMemo(\n () => setViewportHtml({ worldUrl, meshOrImageUrl: propUrl, camera }),\n [worldUrl, propUrl, camera],\n );\n /**\n * A live WebGL context with its own three.js runtime is the single most\n * expensive thing this app can hold. It rebuilds from `html` on return, so\n * release it once the viewport is off-screen or the app is backgrounded.\n */\n const shouldRender = useWebViewRetention({ releaseOnBlur: true });\n\n if (webRef) {\n webRef.current = {\n capture: () => {\n ref.current?.injectJavaScript?.(\n `window.dispatchEvent(new MessageEvent('message',{data:JSON.stringify({type:'capture'})}));true;`,\n );\n },\n setCamera: (c) => {\n ref.current?.injectJavaScript?.(\n `window.dispatchEvent(new MessageEvent('message',{data:${JSON.stringify(\n JSON.stringify({ type: 'setCamera', camera: c }),\n )}}));true;`,\n );\n },\n };\n }\n\n if (!WebView || !shouldRender) return <View style={styles.fallback} />;\n\n return (\n <WebView\n ref={(node: { injectJavaScript?: (js: string) => void } | null) => {\n ref.current = node;\n }}\n originWhitelist={['*']}\n source={{ html }}\n style={styles.view}\n onMessage={(event: { nativeEvent: { data: string } }) => {\n try {\n const msg = JSON.parse(event.nativeEvent.data) as { type?: string; dataUrl?: string };\n if (msg.type === 'frame' && msg.dataUrl) onFrame?.(msg.dataUrl);\n } catch {\n /* ignore */\n }\n }}\n allowFileAccess\n mixedContentMode=\"always\"\n />\n );\n}\n\nconst styles = StyleSheet.create({\n view: { flex: 1, backgroundColor: '#0B0B0E', minHeight: 220 },\n fallback: { flex: 1, minHeight: 220, backgroundColor: '#1F1F24' },\n});\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SetViewportWebView.js","sources":["../../../../src/features/canvas/set/SetViewportWebView.tsx"],"sourcesContent":["/**\n * Three.js set viewport in a WebView — orbit + still capture for native Set studio.\n * Full desktop SetEditor gizmos stay on web; this ships the interactive viewport\n * and the set's objects (stock kit pieces, generated GLBs, image planes), kept\n * in step with the native SetProject through postMessage:\n *\n * objects the whole list - added, moved and removed objects diffed by id\n * addObject / moveObject / removeObject one object at a time\n * setCamera / setLight / frameObject / capture\n *\n * The page answers `ready`, `frame`, `loadError` and `objectError`.\n */\nimport React, { useEffect, useMemo, useRef } from 'react';\nimport { StyleSheet, View } from 'react-native';\nimport { DEFAULT_LIGHTING, MESH_TARGET_HEIGHT_M, type SetCamera, type SetLighting, type SetObject } from './setProject';\nimport { KIT_ENTRIES } from './supplyLibrary';\nimport { useWebViewRetention } from '../../../hooks/useWebViewRetention';\n\nfunction loadWebView(): React.ComponentType<Record<string, unknown>> | null {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports, global-require\n return require('react-native-webview').WebView as React.ComponentType<Record<string, unknown>>;\n } catch {\n return null;\n }\n}\n\nfunction esc(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/</g, '&lt;');\n}\n\n/** What the page needs of each kit entry: its parts and colour, by id. */\nconst KIT_FOR_PAGE = JSON.stringify(\n Object.fromEntries(KIT_ENTRIES.map((entry) => [entry.id, { parts: entry.parts, color: entry.color }])),\n).replace(/</g, '\\\\u003c');\n\n/** A message the page understands - pure, so the wire contract is testable. */\nexport type SetViewportMessage =\n | { type: 'capture' }\n | { type: 'setCamera'; camera: SetCamera }\n | { type: 'objects'; objects: SetObject[] }\n | { type: 'addObject'; object: SetObject }\n | { type: 'moveObject'; object: Pick<SetObject, 'id' | 'position' | 'rotation' | 'scale'> & { hidden?: boolean } }\n | { type: 'removeObject'; id: string }\n | { type: 'setLight'; lighting: SetLighting }\n | { type: 'frameObject'; id: string };\n\n/** The JS that delivers one message to the page's handler. */\nexport function viewportMessageScript(msg: SetViewportMessage): string {\n return `window.dispatchEvent(new MessageEvent('message',{data:${JSON.stringify(JSON.stringify(msg))}}));true;`;\n}\n\nfunction setViewportHtml(opts: { worldUrl?: string; meshOrImageUrl?: string; camera: SetCamera }): string {\n const world = opts.worldUrl ? esc(opts.worldUrl) : '';\n const prop = opts.meshOrImageUrl ? esc(opts.meshOrImageUrl) : '';\n const cam = opts.camera;\n return `<!DOCTYPE html><html><head>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<style>html,body{margin:0;height:100%;background:#0b0b0e;overflow:hidden}canvas{display:block;width:100%;height:100%}</style>\n</head><body>\n<script type=\"importmap\">{\"imports\":{\"three\":\"https://unpkg.com/three@0.160.0/build/three.module.js\",\"three/addons/\":\"https://unpkg.com/three@0.160.0/examples/jsm/\"}}</script>\n<script type=\"module\">\nimport * as THREE from 'three';\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\nconst worldUrl=${JSON.stringify(world)};\nconst propUrl=${JSON.stringify(prop)};\nconst KIT=${KIT_FOR_PAGE};\nconst MESH_H=${MESH_TARGET_HEIGHT_M};\nconst D2R=Math.PI/180;\nconst scene=new THREE.Scene();\nscene.background=new THREE.Color(0x1a1a1e);\nconst camera=new THREE.PerspectiveCamera(${cam.fov},1,0.05,200);\ncamera.position.set(${cam.position.x},${cam.position.y},${cam.position.z});\n/* preserveDrawingBuffer stays FALSE: it doubles the framebuffer and stops the\n driver discarding it after compositing. capture() re-renders synchronously\n immediately before toDataURL(), which reads valid pixels without it. */\nconst renderer=new THREE.WebGLRenderer({antialias:true,preserveDrawingBuffer:false});\nrenderer.setPixelRatio(Math.min(devicePixelRatio,2));\ndocument.body.appendChild(renderer.domElement);\nconst controls=new OrbitControls(camera,renderer.domElement);\ncontrols.target.set(${cam.target.x},${cam.target.y},${cam.target.z});\ncontrols.enableDamping=true;\nconst light=new THREE.DirectionalLight(0xffffff,1.1);\nlight.position.set(2,4,3);scene.add(light);\nconst ambient=new THREE.AmbientLight(0xffffff,0.45);scene.add(ambient);\nconst ground=new THREE.Mesh(new THREE.CircleGeometry(4,48),new THREE.MeshStandardMaterial({color:0x2a2a30,roughness:0.9}));\nground.rotation.x=-Math.PI/2;scene.add(ground);\nfunction fit(){const w=innerWidth,h=innerHeight;camera.aspect=w/Math.max(1,h);camera.updateProjectionMatrix();renderer.setSize(w,h,false);}\naddEventListener('resize',fit);fit();\nfunction post(type,payload){window.ReactNativeWebView&&window.ReactNativeWebView.postMessage(JSON.stringify({type,...payload}));}\n/* A generated model or world that will not load must say so, not draw nothing. */\nfunction failed(what){return ()=>post('loadError',{what});}\nif(worldUrl){new THREE.TextureLoader().load(worldUrl,tex=>{tex.mapping=THREE.EquirectangularReflectionMapping;scene.background=tex;scene.environment=tex;},undefined,failed('world'));}\nlet gltfLoader=null;\nfunction loadGlb(url,ok,bad){\n (gltfLoader?Promise.resolve(gltfLoader):import('three/addons/loaders/GLTFLoader.js').then(({GLTFLoader})=>(gltfLoader=new GLTFLoader())))\n .then(l=>l.load(url,ok,undefined,bad)).catch(bad);\n}\nif(propUrl){\n if(/\\\\.glb($|\\\\?)/i.test(propUrl)){\n loadGlb(propUrl,g=>{const root=g.scene;root.position.set(0,0,0);scene.add(root);},failed('object'));\n } else {\n new THREE.TextureLoader().load(propUrl,tex=>{\n const aspect=tex.image.width/Math.max(1,tex.image.height);\n const w=1.2,h=w/aspect;\n const mesh=new THREE.Mesh(new THREE.PlaneGeometry(w,h),new THREE.MeshBasicMaterial({map:tex,transparent:true}));\n mesh.position.set(0,h/2,0);scene.add(mesh);\n },undefined,failed('object'));\n }\n}\n/* ── The set's objects ─────────────────────────────────────────────────── */\nconst live=new Map();\nfunction partGeometry(p){\n switch(p.shape){\n case 'box':return new THREE.BoxGeometry(p.a,p.b??p.a,p.c??p.a);\n case 'sphere':return new THREE.SphereGeometry(p.a,24,16);\n case 'cylinder':return new THREE.CylinderGeometry(p.a,p.a,p.b??1,24);\n case 'cone':return new THREE.ConeGeometry(p.a,p.b??1,24);\n case 'torus':return new THREE.TorusGeometry(p.a,p.b??0.1,12,32);\n case 'capsule':return new THREE.CapsuleGeometry(p.a,p.b??0.5,6,12);\n case 'plane':{const g=new THREE.PlaneGeometry(p.a,p.b??p.a);g.rotateX(-Math.PI/2);g.translate(0,0.003,0);return g;}\n default:return new THREE.BoxGeometry(0.3,0.3,0.3);\n }\n}\nfunction placeholder(holder,why){\n const box=new THREE.Mesh(new THREE.BoxGeometry(0.4,0.4,0.4),new THREE.MeshBasicMaterial({color:0xd97b3f,wireframe:true}));\n box.position.y=0.2;holder.add(box);\n if(why)post('objectError',{id:holder.userData.id,why});\n}\nfunction buildKit(o,holder){\n const e=KIT[o.kitId];\n if(!e){placeholder(holder,'unknown kit '+o.kitId);return;}\n for(const p of e.parts){\n const m=new THREE.Mesh(partGeometry(p),new THREE.MeshStandardMaterial({color:p.color||o.color||e.color,roughness:0.8}));\n if(p.offset)m.position.set(p.offset[0],p.offset[1],p.offset[2]);\n if(p.rotate)m.rotation.set(p.rotate[0]*D2R,p.rotate[1]*D2R,p.rotate[2]*D2R);\n holder.add(m);\n }\n}\nfunction buildMesh(o,holder){\n if(!o.modelUrl){placeholder(holder,'no model');return;}\n loadGlb(o.modelUrl,g=>{\n const root=g.scene;\n const box=new THREE.Box3().setFromObject(root);const size=box.getSize(new THREE.Vector3());\n const k=size.y>0?MESH_H/size.y:1;root.scale.setScalar(k);\n const c=box.getCenter(new THREE.Vector3());root.position.set(-c.x*k,-box.min.y*k,-c.z*k);\n holder.add(root);\n },()=>{placeholder(holder,'the model would not load');post('loadError',{what:'object'});});\n}\nfunction buildImage(o,holder){\n if(!o.imageUrl){placeholder(holder,'no image');return;}\n new THREE.TextureLoader().load(o.imageUrl,tex=>{\n const w=o.imageWidthM||1,h=w*tex.image.height/Math.max(1,tex.image.width);\n const m=new THREE.Mesh(new THREE.PlaneGeometry(w,h),new THREE.MeshBasicMaterial({map:tex,transparent:true,side:THREE.DoubleSide}));\n m.position.y=h/2;holder.add(m);\n },undefined,()=>{placeholder(holder,'the image would not load');post('loadError',{what:'object'});});\n}\nconst sigOf=o=>[o.source,o.kitId||o.modelUrl||o.imageUrl||'',o.color||'',o.imageWidthM||''].join('|');\nfunction place(holder,o){\n if(o.position)holder.position.set(o.position.x,o.position.y,o.position.z);\n if(o.rotation)holder.rotation.set(o.rotation.x*D2R,o.rotation.y*D2R,o.rotation.z*D2R);\n if(o.scale)holder.scale.set(o.scale.x,o.scale.y,o.scale.z);\n if(typeof o.hidden==='boolean')holder.visible=!o.hidden;\n}\nfunction addObject(o){\n const prev=live.get(o.id);\n if(prev&&prev.sig===sigOf(o)){place(prev.holder,o);return;}\n if(prev)scene.remove(prev.holder);\n const holder=new THREE.Group();holder.userData.id=o.id;\n if(o.source==='kit')buildKit(o,holder);else if(o.source==='mesh')buildMesh(o,holder);else buildImage(o,holder);\n place(holder,o);scene.add(holder);live.set(o.id,{holder,sig:sigOf(o)});\n}\nfunction removeObject(id){const rec=live.get(id);if(rec){scene.remove(rec.holder);live.delete(id);}}\nfunction syncObjects(list){\n const keep=new Set(list.map(o=>o.id));\n for(const id of [...live.keys()])if(!keep.has(id))removeObject(id);\n list.forEach(addObject);\n}\nfunction setLight(l){\n const az=l.keyAzimuthDeg*D2R,el=l.keyElevationDeg*D2R;\n light.position.set(Math.sin(az)*Math.cos(el)*6,Math.sin(el)*6,Math.cos(az)*Math.cos(el)*6);\n light.intensity=l.keyIntensity*0.42;ambient.intensity=l.ambientIntensity*0.82;\n}\nfunction frameObject(id){\n const rec=live.get(id);if(!rec)return;\n const box=new THREE.Box3().setFromObject(rec.holder);if(box.isEmpty())return;\n const c=box.getCenter(new THREE.Vector3());const r=Math.max(0.15,box.getSize(new THREE.Vector3()).length()/2);\n const dir=camera.position.clone().sub(controls.target).normalize();\n controls.target.copy(c);camera.position.copy(c.clone().add(dir.multiplyScalar(r*3)));controls.update();\n}\nfunction tick(){controls.update();renderer.render(scene,camera);requestAnimationFrame(tick);}tick();\nfunction capture(){renderer.render(scene,camera);return renderer.domElement.toDataURL('image/jpeg',0.92);}\nfunction onMessage(ev){\n let msg={};try{msg=JSON.parse(ev.data);}catch(e){return;}\n if(msg.type==='capture'){post('frame',{dataUrl:capture()});}\n if(msg.type==='setCamera'&&msg.camera){\n const c=msg.camera;camera.position.set(c.position.x,c.position.y,c.position.z);\n if(c.fov){camera.fov=c.fov;camera.updateProjectionMatrix();}\n controls.target.set(c.target.x,c.target.y,c.target.z);controls.update();\n }\n if(msg.type==='objects'&&Array.isArray(msg.objects))syncObjects(msg.objects);\n if(msg.type==='addObject'&&msg.object)addObject(msg.object);\n if(msg.type==='moveObject'&&msg.object){const rec=live.get(msg.object.id);if(rec)place(rec.holder,msg.object);}\n if(msg.type==='removeObject'&&msg.id)removeObject(msg.id);\n if(msg.type==='setLight'&&msg.lighting)setLight(msg.lighting);\n if(msg.type==='frameObject'&&msg.id)frameObject(msg.id);\n}\naddEventListener('message',onMessage);\ndocument.addEventListener('message',onMessage);\npost('ready',{});\n</script></body></html>`;\n}\n\nexport interface SetViewportApi {\n capture: () => void;\n setCamera: (c: SetCamera) => void;\n addObject: (object: SetObject) => void;\n moveObject: (object: SetObject) => void;\n removeObject: (id: string) => void;\n syncObjects: (objects: SetObject[]) => void;\n setLight: (lighting: SetLighting) => void;\n frameObject: (id: string) => void;\n}\n\nexport function SetViewportWebView({\n worldUrl,\n propUrl,\n camera,\n objects,\n lighting,\n onFrame,\n onLoadError,\n webRef,\n}: {\n worldUrl?: string;\n propUrl?: string;\n camera: SetCamera;\n /** The set's objects; the page is kept in step with every change. */\n objects?: SetObject[];\n lighting?: SetLighting;\n onFrame?: (dataUrl: string) => void;\n /** The world backdrop or the 3D object failed to load in the viewport. */\n onLoadError?: (what: 'world' | 'object') => void;\n webRef?: React.MutableRefObject<SetViewportApi | null>;\n}) {\n const WebView = loadWebView();\n const ref = useRef<{ injectJavaScript?: (js: string) => void } | null>(null);\n // The page is built once per world/prop; later camera moves are messages, not reloads.\n const cameraRef = useRef(camera);\n cameraRef.current = camera;\n const objectsRef = useRef(objects);\n objectsRef.current = objects;\n const lightingRef = useRef(lighting);\n lightingRef.current = lighting;\n const html = useMemo(\n () => setViewportHtml({ worldUrl, meshOrImageUrl: propUrl, camera: cameraRef.current }),\n [worldUrl, propUrl],\n );\n /**\n * A live WebGL context with its own three.js runtime is the single most\n * expensive thing this app can hold. It rebuilds from `html` on return, so\n * release it once the viewport is off-screen or the app is backgrounded.\n */\n const shouldRender = useWebViewRetention({ releaseOnBlur: true });\n\n const send = (msg: SetViewportMessage) => ref.current?.injectJavaScript?.(viewportMessageScript(msg));\n\n if (webRef) {\n webRef.current = {\n capture: () => send({ type: 'capture' }),\n setCamera: (c) => send({ type: 'setCamera', camera: c }),\n addObject: (object) => send({ type: 'addObject', object }),\n moveObject: (object) =>\n send({\n type: 'moveObject',\n object: {\n id: object.id,\n position: object.position,\n rotation: object.rotation,\n scale: object.scale,\n hidden: Boolean(object.hidden),\n },\n }),\n removeObject: (id) => send({ type: 'removeObject', id }),\n syncObjects: (list) => send({ type: 'objects', objects: list }),\n setLight: (l) => send({ type: 'setLight', lighting: l }),\n frameObject: (id) => send({ type: 'frameObject', id }),\n };\n }\n\n useEffect(() => {\n if (objects) send({ type: 'objects', objects });\n }, [objects]);\n useEffect(() => {\n if (lighting) send({ type: 'setLight', lighting });\n }, [lighting]);\n const firstCamera = useRef(true);\n useEffect(() => {\n if (firstCamera.current) {\n firstCamera.current = false;\n return;\n }\n send({ type: 'setCamera', camera });\n }, [camera]);\n\n if (!WebView || !shouldRender) return <View style={styles.fallback} />;\n\n return (\n <WebView\n ref={(node: { injectJavaScript?: (js: string) => void } | null) => {\n ref.current = node;\n }}\n originWhitelist={['*']}\n source={{ html }}\n style={styles.view}\n onMessage={(event: { nativeEvent: { data: string } }) => {\n try {\n const msg = JSON.parse(event.nativeEvent.data) as {\n type?: string;\n dataUrl?: string;\n what?: string;\n };\n if (msg.type === 'frame' && msg.dataUrl) onFrame?.(msg.dataUrl);\n if (msg.type === 'loadError') onLoadError?.(msg.what === 'world' ? 'world' : 'object');\n if (msg.type === 'ready') {\n // A fresh page (first load, a new world, a return from the background) starts empty.\n send({ type: 'setLight', lighting: lightingRef.current ?? DEFAULT_LIGHTING });\n if (objectsRef.current?.length) send({ type: 'objects', objects: objectsRef.current });\n send({ type: 'setCamera', camera: cameraRef.current });\n }\n } catch {\n /* ignore */\n }\n }}\n allowFileAccess\n mixedContentMode=\"always\"\n />\n );\n}\n\nconst styles = StyleSheet.create({\n view: { flex: 1, backgroundColor: '#0B0B0E', minHeight: 220 },\n fallback: { flex: 1, minHeight: 220, backgroundColor: '#1F1F24' },\n});\n"],"names":[],"mappings":"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"}