@agent-native/core 0.90.1 → 0.90.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
- package/corpus/core/src/client/use-action.ts +39 -2
- package/corpus/core/src/client/use-db-sync.ts +44 -18
- package/corpus/core/src/db/create-get-db.ts +32 -2
- package/corpus/core/src/server/framework-request-handler.ts +52 -4
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
- package/corpus/templates/assets/AGENTS.md +23 -4
- package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
- package/corpus/templates/assets/actions/_helpers.ts +8 -1
- package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
- package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
- package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
- package/corpus/templates/assets/actions/generate-image.ts +342 -25
- package/corpus/templates/assets/actions/get-library.ts +4 -3
- package/corpus/templates/assets/actions/navigate.ts +2 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
- package/corpus/templates/assets/actions/update-asset.ts +1 -0
- package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
- package/corpus/templates/assets/actions/view-screen.ts +8 -1
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
- package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
- package/corpus/templates/assets/app/i18n-data.ts +650 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
- package/corpus/templates/assets/app/routes/library.tsx +43 -1
- package/corpus/templates/assets/server/lib/generation.ts +366 -44
- package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
- package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
- package/corpus/templates/assets/shared/api.ts +47 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
- package/corpus/templates/clips/app/routes/record.tsx +157 -68
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
- package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
- package/corpus/templates/content/AGENTS.md +52 -52
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
- package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
- package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
- package/corpus/templates/content/actions/_database-utils.ts +44 -4
- package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
- package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
- package/corpus/templates/content/actions/delete-content-database.ts +1 -0
- package/corpus/templates/content/actions/get-document.ts +11 -2
- package/corpus/templates/content/actions/list-content-databases.ts +57 -9
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
- package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
- package/corpus/templates/content/actions/restore-content-database.ts +1 -0
- package/corpus/templates/content/actions/update-document.ts +43 -0
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
- package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
- package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
- package/corpus/templates/content/app/global.css +41 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
- package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
- package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
- package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
- package/corpus/templates/content/app/i18n-data.ts +355 -0
- package/corpus/templates/content/app/root.tsx +22 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
- package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
- package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
- package/corpus/templates/content/shared/api.ts +13 -0
- package/corpus/templates/content/shared/builder-mdx.ts +16 -3
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
- package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
- package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
- package/corpus/templates/design/AGENTS.md +9 -2
- package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
- package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
- package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
- package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
- package/corpus/templates/design/actions/create-component.ts +29 -60
- package/corpus/templates/design/actions/create-file.ts +9 -2
- package/corpus/templates/design/actions/duplicate-design.ts +31 -2
- package/corpus/templates/design/actions/edit-design.ts +36 -38
- package/corpus/templates/design/actions/generate-design-title.ts +87 -0
- package/corpus/templates/design/actions/generate-design.ts +53 -18
- package/corpus/templates/design/actions/generate-screens.ts +108 -4
- package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
- package/corpus/templates/design/actions/insert-asset.ts +34 -29
- package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
- package/corpus/templates/design/actions/present-design-variants.ts +7 -2
- package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
- package/corpus/templates/design/actions/update-file.ts +190 -75
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
- package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
- package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
- package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
- package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
- package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
- package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
- package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
- package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
- package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
- package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
- package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
- package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
- package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
- package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
- package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
- package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
- package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
- package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
- package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
- package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
- package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
- package/corpus/templates/design/app/components/design/types.ts +15 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
- package/corpus/templates/design/app/global.css +8 -8
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
- package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
- package/corpus/templates/design/app/i18n-data.ts +1664 -152
- package/corpus/templates/design/app/i18n-motion.ts +563 -0
- package/corpus/templates/design/app/lib/design-import.ts +73 -0
- package/corpus/templates/design/app/pages/Index.tsx +41 -22
- package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
- package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
- package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
- package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
- package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
- package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
- package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
- package/corpus/templates/design/server/source-workspace.ts +139 -59
- package/corpus/templates/design/shared/board-file.ts +11 -2
- package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
- package/corpus/templates/design/shared/canvas-math.ts +354 -35
- package/corpus/templates/design/shared/code-layer.ts +277 -8
- package/corpus/templates/design/shared/design-state.ts +11 -4
- package/corpus/templates/design/shared/interaction-states.ts +548 -0
- package/corpus/templates/design/shared/motion-compiler.ts +186 -23
- package/corpus/templates/design/shared/motion-easing.ts +294 -0
- package/corpus/templates/design/shared/motion-timeline.ts +400 -34
- package/corpus/templates/design/shared/prompt-title.ts +95 -0
- package/corpus/templates/design/shared/responsive-classes.ts +448 -10
- package/corpus/templates/design/shared/screen-annotation.ts +46 -0
- package/corpus/templates/design/shared/shader-fills.ts +1009 -0
- package/corpus/templates/design/shared/shader-presets.ts +852 -4
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
- package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
- package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
- package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
- package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
- package/dist/client/use-action.d.ts +3 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +34 -3
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +4 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +37 -18
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/create-get-db.d.ts +25 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +25 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +37 -1
- package/dist/server/framework-request-handler.js.map +1 -1
- package/package.json +1 -1
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
- package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
|
@@ -1378,6 +1378,23 @@ export class RecorderEngine {
|
|
|
1378
1378
|
};
|
|
1379
1379
|
this.lastFinalizeMeta = finalizeMeta;
|
|
1380
1380
|
|
|
1381
|
+
// Stop camera and mic hardware immediately — privacy-sensitive inputs no
|
|
1382
|
+
// longer needed once the final chunk is flushed. The composite and display
|
|
1383
|
+
// streams are intentionally left alive: the caller holds a compositeStream
|
|
1384
|
+
// reference for an unawaited thumbnail capture in screen+camera mode.
|
|
1385
|
+
// Full stream teardown happens in the finally block below.
|
|
1386
|
+
this.cameraLive = false;
|
|
1387
|
+
this.audioMixSources = [];
|
|
1388
|
+
this.audioMixCtx?.close().catch(() => {});
|
|
1389
|
+
this.audioMixCtx = null;
|
|
1390
|
+
for (const s of [this.cameraStream, this.rawCameraStream, this.micStream]) {
|
|
1391
|
+
s?.getTracks().forEach((t) => {
|
|
1392
|
+
try {
|
|
1393
|
+
t.stop();
|
|
1394
|
+
} catch {}
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1381
1398
|
// Drain any legacy in-flight chunk uploads before we either compress or
|
|
1382
1399
|
// upload. New recordings upload after stop(), but keeping this guard makes
|
|
1383
1400
|
// retry paths resilient while a release rolls out.
|
|
@@ -1929,41 +1946,99 @@ export class RecorderEngine {
|
|
|
1929
1946
|
// Keep binary uploads comfortably under Netlify's effective function
|
|
1930
1947
|
// payload limit. This mirrors the local-file upload path in record.tsx.
|
|
1931
1948
|
const UPLOAD_SLICE_BYTES = 3 * 1024 * 1024;
|
|
1949
|
+
const PARALLELISM = 4;
|
|
1932
1950
|
const totalSlices = Math.max(1, Math.ceil(blob.size / UPLOAD_SLICE_BYTES));
|
|
1933
1951
|
|
|
1934
|
-
|
|
1935
|
-
for (let i = 0; i < totalSlices; i++) {
|
|
1936
|
-
if (signal?.aborted) {
|
|
1937
|
-
throw signal.reason instanceof Error
|
|
1938
|
-
? signal.reason
|
|
1939
|
-
: new Error("Upload aborted");
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1952
|
+
const slices = Array.from({ length: totalSlices }, (_, i) => {
|
|
1942
1953
|
const start = i * UPLOAD_SLICE_BYTES;
|
|
1943
1954
|
const end = Math.min(start + UPLOAD_SLICE_BYTES, blob.size);
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1955
|
+
return {
|
|
1956
|
+
index: this.chunkIndex++,
|
|
1957
|
+
slice: blob.slice(start, end, mimeType),
|
|
1958
|
+
isFinal: i === totalSlices - 1,
|
|
1959
|
+
};
|
|
1960
|
+
});
|
|
1961
|
+
const finalSlice = slices[slices.length - 1];
|
|
1962
|
+
const parallelSlices = slices.slice(0, -1);
|
|
1963
|
+
|
|
1964
|
+
const results = new Array<Record<string, unknown> | undefined>(totalSlices);
|
|
1965
|
+
const queue = parallelSlices.slice();
|
|
1966
|
+
const chunkAbort = new AbortController();
|
|
1967
|
+
if (signal?.aborted) {
|
|
1968
|
+
chunkAbort.abort(signal.reason);
|
|
1969
|
+
} else {
|
|
1970
|
+
signal?.addEventListener("abort", () => chunkAbort.abort(signal.reason), {
|
|
1971
|
+
once: true,
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
let uploadError: Error | null = null;
|
|
1975
|
+
let completedCount = 0;
|
|
1976
|
+
|
|
1977
|
+
const worker = async () => {
|
|
1978
|
+
while (queue.length > 0) {
|
|
1979
|
+
if (chunkAbort.signal.aborted) break;
|
|
1980
|
+
const item = queue.shift();
|
|
1981
|
+
if (!item) break;
|
|
1982
|
+
const { index, slice } = item;
|
|
1983
|
+
try {
|
|
1984
|
+
results[index] = await this.uploadChunk(slice, index, {
|
|
1985
|
+
isFinal: false,
|
|
1986
|
+
total: totalSlices,
|
|
1987
|
+
mimeType,
|
|
1988
|
+
signal: chunkAbort.signal,
|
|
1989
|
+
});
|
|
1990
|
+
this.opts.onChunk?.({
|
|
1991
|
+
index: completedCount++,
|
|
1992
|
+
bytes: slice.size,
|
|
1993
|
+
total: totalSlices,
|
|
1994
|
+
});
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
if (chunkAbort.signal.aborted) return;
|
|
1997
|
+
if (!uploadError) {
|
|
1998
|
+
uploadError = err instanceof Error ? err : new Error(String(err));
|
|
1999
|
+
chunkAbort.abort(uploadError);
|
|
2000
|
+
}
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
await Promise.all(
|
|
2007
|
+
Array.from(
|
|
2008
|
+
{ length: Math.min(PARALLELISM, parallelSlices.length) },
|
|
2009
|
+
worker,
|
|
2010
|
+
),
|
|
2011
|
+
);
|
|
2012
|
+
|
|
2013
|
+
if (uploadError) throw uploadError;
|
|
2014
|
+
if (signal?.aborted) {
|
|
2015
|
+
throw signal.reason instanceof Error
|
|
2016
|
+
? signal.reason
|
|
2017
|
+
: new Error("Upload aborted");
|
|
2018
|
+
}
|
|
1947
2019
|
|
|
1948
|
-
|
|
1949
|
-
|
|
2020
|
+
results[finalSlice.index] = await this.uploadChunk(
|
|
2021
|
+
finalSlice.slice,
|
|
2022
|
+
finalSlice.index,
|
|
2023
|
+
{
|
|
2024
|
+
isFinal: true,
|
|
1950
2025
|
total: totalSlices,
|
|
1951
2026
|
mimeType,
|
|
1952
|
-
durationMs:
|
|
1953
|
-
width:
|
|
1954
|
-
height:
|
|
1955
|
-
hasAudio:
|
|
1956
|
-
hasCamera:
|
|
2027
|
+
durationMs: meta.durationMs,
|
|
2028
|
+
width: meta.dimensions.width,
|
|
2029
|
+
height: meta.dimensions.height,
|
|
2030
|
+
hasAudio: meta.hasAudio,
|
|
2031
|
+
hasCamera: meta.hasCamera,
|
|
1957
2032
|
signal,
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
}
|
|
2033
|
+
},
|
|
2034
|
+
);
|
|
2035
|
+
this.opts.onChunk?.({
|
|
2036
|
+
index: finalSlice.index,
|
|
2037
|
+
bytes: finalSlice.slice.size,
|
|
2038
|
+
total: totalSlices,
|
|
2039
|
+
});
|
|
1965
2040
|
|
|
1966
|
-
return
|
|
2041
|
+
return results[finalSlice.index];
|
|
1967
2042
|
}
|
|
1968
2043
|
|
|
1969
2044
|
private async uploadChunk(
|
|
@@ -2021,7 +2096,9 @@ export class RecorderEngine {
|
|
|
2021
2096
|
uploadErr.name === "AbortError"
|
|
2022
2097
|
) {
|
|
2023
2098
|
if (extra.isFinal && uploadErr.name !== "AbortError") {
|
|
2024
|
-
const recovered = await this.recoverReadyAfterFinalUploadError(
|
|
2099
|
+
const recovered = await this.recoverReadyAfterFinalUploadError(
|
|
2100
|
+
extra.signal,
|
|
2101
|
+
);
|
|
2025
2102
|
if (recovered) return recovered;
|
|
2026
2103
|
}
|
|
2027
2104
|
throw uploadErr;
|
|
@@ -2040,7 +2117,9 @@ export class RecorderEngine {
|
|
|
2040
2117
|
if (extra.isFinal && res.status === 504) {
|
|
2041
2118
|
triedFinalUploadRecovery = true;
|
|
2042
2119
|
await res.text().catch(() => "");
|
|
2043
|
-
const recovered = await this.recoverReadyAfterFinalUploadError(
|
|
2120
|
+
const recovered = await this.recoverReadyAfterFinalUploadError(
|
|
2121
|
+
extra.signal,
|
|
2122
|
+
);
|
|
2044
2123
|
if (recovered) return recovered;
|
|
2045
2124
|
break;
|
|
2046
2125
|
}
|
|
@@ -2075,7 +2154,9 @@ export class RecorderEngine {
|
|
|
2075
2154
|
!triedFinalUploadRecovery &&
|
|
2076
2155
|
this.isFinalUploadRecoveryCandidate(res.status, err)
|
|
2077
2156
|
) {
|
|
2078
|
-
const recovered = await this.recoverReadyAfterFinalUploadError(
|
|
2157
|
+
const recovered = await this.recoverReadyAfterFinalUploadError(
|
|
2158
|
+
extra.signal,
|
|
2159
|
+
);
|
|
2079
2160
|
if (recovered) return recovered;
|
|
2080
2161
|
}
|
|
2081
2162
|
trackClipUploadBlockingFailure({
|
|
@@ -2153,14 +2234,14 @@ export class RecorderEngine {
|
|
|
2153
2234
|
return !/too large|exceeds.*limit|chunk too large/i.test(error.message);
|
|
2154
2235
|
}
|
|
2155
2236
|
|
|
2156
|
-
private async recoverReadyAfterFinalUploadError(
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
> | null> {
|
|
2237
|
+
private async recoverReadyAfterFinalUploadError(
|
|
2238
|
+
signal?: AbortSignal,
|
|
2239
|
+
): Promise<Record<string, unknown> | null> {
|
|
2160
2240
|
return waitForReadyRecordingAfterFinalizeError({
|
|
2161
2241
|
uploadUrl: this.opts.uploadUrl,
|
|
2162
2242
|
recordingId: this.opts.recordingId,
|
|
2163
2243
|
preferAuthenticated: true,
|
|
2244
|
+
signal,
|
|
2164
2245
|
});
|
|
2165
2246
|
}
|
|
2166
2247
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client";
|
|
2
|
+
import { Button } from "@agent-native/toolkit/ui/button";
|
|
3
|
+
import { IconPlayerRecord } from "@tabler/icons-react";
|
|
4
|
+
import { NavLink } from "react-router";
|
|
5
|
+
|
|
1
6
|
import { LibraryGrid } from "@/components/library/library-grid";
|
|
2
7
|
|
|
3
8
|
const SEO_TITLE =
|
|
@@ -18,5 +23,26 @@ export function meta() {
|
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export default function LibraryIndexRoute() {
|
|
21
|
-
|
|
26
|
+
const t = useT();
|
|
27
|
+
return (
|
|
28
|
+
<LibraryGrid
|
|
29
|
+
view="library"
|
|
30
|
+
folderId={null}
|
|
31
|
+
title="Library"
|
|
32
|
+
extraActions={
|
|
33
|
+
<Button
|
|
34
|
+
className="gap-1.5 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90"
|
|
35
|
+
size="sm"
|
|
36
|
+
asChild
|
|
37
|
+
>
|
|
38
|
+
<NavLink to="/record" aria-label={t("navigation.newRecording")}>
|
|
39
|
+
<IconPlayerRecord className="h-4 w-4" />
|
|
40
|
+
<span className="hidden sm:inline">
|
|
41
|
+
{t("navigation.newRecording")}
|
|
42
|
+
</span>
|
|
43
|
+
</NavLink>
|
|
44
|
+
</Button>
|
|
45
|
+
}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
22
48
|
}
|
|
@@ -1357,6 +1357,7 @@ export default function RecordRoute() {
|
|
|
1357
1357
|
// upload pipeline so finalize-recording handles it identically.
|
|
1358
1358
|
// -------------------------------------------------------------------------
|
|
1359
1359
|
const UPLOAD_CHUNK_BYTES = 3 * 1024 * 1024;
|
|
1360
|
+
const UPLOAD_PARALLELISM = 4;
|
|
1360
1361
|
|
|
1361
1362
|
const probeVideoMetadata = useCallback(
|
|
1362
1363
|
(
|
|
@@ -1602,92 +1603,180 @@ export default function RecordRoute() {
|
|
|
1602
1603
|
1,
|
|
1603
1604
|
Math.ceil(uploadBlob.size / UPLOAD_CHUNK_BYTES),
|
|
1604
1605
|
);
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
if (isStale()) throw makeAbortError("Upload cancelled");
|
|
1606
|
+
|
|
1607
|
+
const chunkDescs = Array.from({ length: totalChunks }, (_, i) => {
|
|
1608
1608
|
const start = i * UPLOAD_CHUNK_BYTES;
|
|
1609
1609
|
const end = Math.min(start + UPLOAD_CHUNK_BYTES, uploadBlob.size);
|
|
1610
|
-
const slice = uploadBlob.slice(start, end, uploadMimeType);
|
|
1611
1610
|
const isFinal = i === totalChunks - 1;
|
|
1612
|
-
|
|
1611
|
+
return {
|
|
1613
1612
|
index: i,
|
|
1614
|
-
|
|
1613
|
+
slice: uploadBlob.slice(start, end, uploadMimeType),
|
|
1615
1614
|
isFinal,
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1615
|
+
url: chunkUploadUrl(uploadBase, {
|
|
1616
|
+
index: i,
|
|
1617
|
+
total: totalChunks,
|
|
1618
|
+
isFinal,
|
|
1619
|
+
mimeType: uploadMimeType,
|
|
1620
|
+
durationMs: isFinal ? meta.durationMs : undefined,
|
|
1621
|
+
width: isFinal ? meta.width : undefined,
|
|
1622
|
+
height: isFinal ? meta.height : undefined,
|
|
1623
|
+
hasAudio: isFinal ? true : undefined,
|
|
1624
|
+
hasCamera: isFinal ? false : undefined,
|
|
1625
|
+
}),
|
|
1626
|
+
};
|
|
1627
|
+
});
|
|
1628
|
+
const finalChunkDesc = chunkDescs[chunkDescs.length - 1];
|
|
1629
|
+
const parallelChunks = chunkDescs.slice(0, -1);
|
|
1630
|
+
|
|
1631
|
+
const chunkAbort = new AbortController();
|
|
1632
|
+
if (abort.signal.aborted) {
|
|
1633
|
+
chunkAbort.abort(abort.signal.reason);
|
|
1634
|
+
} else {
|
|
1635
|
+
abort.signal.addEventListener(
|
|
1636
|
+
"abort",
|
|
1637
|
+
() => chunkAbort.abort(abort.signal.reason),
|
|
1638
|
+
{ once: true },
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
const finalChunk = { result: null as Record<string, unknown> | null };
|
|
1643
|
+
let uploadError: Error | null = null;
|
|
1644
|
+
const queue = parallelChunks.slice();
|
|
1645
|
+
|
|
1646
|
+
const worker = async () => {
|
|
1647
|
+
while (queue.length > 0) {
|
|
1648
|
+
if (isStale() || chunkAbort.signal.aborted) break;
|
|
1649
|
+
const item = queue.shift();
|
|
1650
|
+
if (!item) break;
|
|
1651
|
+
const { index, slice, url } = item;
|
|
1652
|
+
|
|
1653
|
+
let chunkRes: Response;
|
|
1654
|
+
try {
|
|
1655
|
+
chunkRes = await fetch(url, {
|
|
1656
|
+
method: "POST",
|
|
1657
|
+
headers: { "Content-Type": uploadMimeType },
|
|
1658
|
+
body: await slice.arrayBuffer(),
|
|
1659
|
+
signal: chunkAbort.signal,
|
|
1660
|
+
});
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
if (chunkAbort.signal.aborted) return;
|
|
1663
|
+
if (!uploadError) {
|
|
1664
|
+
uploadError =
|
|
1665
|
+
err instanceof Error ? err : new Error(String(err));
|
|
1666
|
+
chunkAbort.abort(uploadError);
|
|
1667
|
+
}
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
if (!chunkRes.ok) {
|
|
1672
|
+
const text = await chunkRes.text().catch(() => "");
|
|
1673
|
+
const error = new Error(
|
|
1674
|
+
t("recordRoute.uploadFailedAtChunk", {
|
|
1675
|
+
chunk: index + 1,
|
|
1676
|
+
total: totalChunks,
|
|
1677
|
+
message: text || chunkRes.statusText,
|
|
1678
|
+
}),
|
|
1679
|
+
);
|
|
1680
|
+
(error as Error & { status?: number }).status = chunkRes.status;
|
|
1681
|
+
if (!uploadError) {
|
|
1682
|
+
uploadError = error;
|
|
1683
|
+
chunkAbort.abort(uploadError);
|
|
1684
|
+
}
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
setUploadProgress((index + 1) / totalChunks);
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
await Promise.all(
|
|
1692
|
+
Array.from(
|
|
1693
|
+
{ length: Math.min(UPLOAD_PARALLELISM, parallelChunks.length) },
|
|
1694
|
+
worker,
|
|
1695
|
+
),
|
|
1696
|
+
);
|
|
1697
|
+
|
|
1698
|
+
if (uploadError) throw uploadError;
|
|
1699
|
+
if (abort.signal.aborted) {
|
|
1700
|
+
const reason = abort.signal.reason;
|
|
1701
|
+
throw reason instanceof Error
|
|
1702
|
+
? reason
|
|
1703
|
+
: makeAbortError("Upload cancelled");
|
|
1704
|
+
}
|
|
1705
|
+
if (isStale()) throw makeAbortError("Upload cancelled");
|
|
1706
|
+
|
|
1707
|
+
const { index, slice, url } = finalChunkDesc;
|
|
1708
|
+
let chunkRes: Response | null = null;
|
|
1709
|
+
try {
|
|
1710
|
+
chunkRes = await fetch(url, {
|
|
1711
|
+
method: "POST",
|
|
1712
|
+
headers: { "Content-Type": uploadMimeType },
|
|
1713
|
+
body: await slice.arrayBuffer(),
|
|
1714
|
+
signal: abort.signal,
|
|
1622
1715
|
});
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
if (
|
|
1718
|
+
createdId &&
|
|
1719
|
+
(err as { name?: string } | null)?.name !== "AbortError"
|
|
1720
|
+
) {
|
|
1721
|
+
const recovered = await waitForReadyRecordingAfterFinalizeError({
|
|
1722
|
+
uploadUrl: uploadBase,
|
|
1723
|
+
recordingId: createdId,
|
|
1724
|
+
preferAuthenticated: true,
|
|
1629
1725
|
signal: abort.signal,
|
|
1630
1726
|
});
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
(err as { name?: string } | null)?.name !== "AbortError"
|
|
1636
|
-
) {
|
|
1637
|
-
const recovered = await waitForReadyRecordingAfterFinalizeError({
|
|
1638
|
-
uploadUrl: uploadBase,
|
|
1639
|
-
recordingId: createdId,
|
|
1640
|
-
preferAuthenticated: true,
|
|
1641
|
-
});
|
|
1642
|
-
if (recovered) {
|
|
1643
|
-
finalChunkResult = recovered;
|
|
1644
|
-
break;
|
|
1645
|
-
}
|
|
1727
|
+
if (recovered) {
|
|
1728
|
+
finalChunk.result = recovered;
|
|
1729
|
+
} else {
|
|
1730
|
+
throw err;
|
|
1646
1731
|
}
|
|
1732
|
+
} else {
|
|
1647
1733
|
throw err;
|
|
1648
1734
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
)
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
if (chunkRes && !chunkRes.ok) {
|
|
1738
|
+
const text = await chunkRes.text().catch(() => "");
|
|
1739
|
+
const error = new Error(
|
|
1740
|
+
t("recordRoute.uploadFailedAtChunk", {
|
|
1741
|
+
chunk: index + 1,
|
|
1742
|
+
total: totalChunks,
|
|
1743
|
+
message: text || chunkRes.statusText,
|
|
1744
|
+
}),
|
|
1745
|
+
);
|
|
1746
|
+
(error as Error & { status?: number }).status = chunkRes.status;
|
|
1747
|
+
if (
|
|
1748
|
+
createdId &&
|
|
1749
|
+
chunkRes.status !== 413 &&
|
|
1750
|
+
!isUploadSizeError(error.message)
|
|
1751
|
+
) {
|
|
1752
|
+
const recovered = await waitForReadyRecordingAfterFinalizeError({
|
|
1753
|
+
uploadUrl: uploadBase,
|
|
1754
|
+
recordingId: createdId,
|
|
1755
|
+
preferAuthenticated: true,
|
|
1756
|
+
signal: abort.signal,
|
|
1757
|
+
});
|
|
1758
|
+
if (recovered) {
|
|
1759
|
+
finalChunk.result = recovered;
|
|
1760
|
+
} else {
|
|
1761
|
+
throw error;
|
|
1674
1762
|
}
|
|
1763
|
+
} else {
|
|
1675
1764
|
throw error;
|
|
1676
1765
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
if (chunkRes?.ok) {
|
|
1769
|
+
finalChunk.result =
|
|
1770
|
+
((await chunkRes.json().catch(() => null)) as Record<
|
|
1771
|
+
string,
|
|
1772
|
+
unknown
|
|
1773
|
+
> | null) ?? null;
|
|
1685
1774
|
}
|
|
1686
1775
|
|
|
1687
1776
|
setUiState("complete");
|
|
1688
1777
|
const waitingForStorage =
|
|
1689
|
-
|
|
1690
|
-
|
|
1778
|
+
finalChunk.result?.waitingForStorage === true ||
|
|
1779
|
+
finalChunk.result?.status === "waiting_storage";
|
|
1691
1780
|
if (waitingForStorage) {
|
|
1692
1781
|
toast.info(t("recordRoute.videoReadyToUpload"), {
|
|
1693
1782
|
description: t("recordRoute.connectStorageToFinish"),
|
|
@@ -75,11 +75,6 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
75
75
|
return { ok: true, recordingId, alreadyFailed: true, chunksCleared: 0 };
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
const cleared = await deleteAppStateByPrefix(
|
|
79
|
-
`recording-chunks-${recordingId}-`,
|
|
80
|
-
);
|
|
81
|
-
await deleteResumableSession(recordingId).catch(() => {});
|
|
82
|
-
|
|
83
78
|
const now = new Date().toISOString();
|
|
84
79
|
await db
|
|
85
80
|
.update(schema.recordings)
|
|
@@ -96,6 +91,11 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
96
91
|
failureReason,
|
|
97
92
|
updatedAt: now,
|
|
98
93
|
});
|
|
94
|
+
|
|
95
|
+
const cleared = await deleteAppStateByPrefix(
|
|
96
|
+
`recording-chunks-${recordingId}-`,
|
|
97
|
+
);
|
|
98
|
+
await deleteResumableSession(recordingId).catch(() => {});
|
|
99
99
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
100
100
|
|
|
101
101
|
return { ok: true, recordingId, chunksCleared: cleared };
|
|
@@ -375,7 +375,11 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
375
375
|
|
|
376
376
|
// Only persist non-empty chunks. The final sentinel can legitimately be
|
|
377
377
|
// empty — writing a zero-byte chunk would just clutter application_state.
|
|
378
|
+
// Check for abort/failure before writing so parallel in-flight requests
|
|
379
|
+
// don't recreate scratch chunk rows after /abort already cleared them.
|
|
378
380
|
if (bytes.byteLength > 0) {
|
|
381
|
+
const failedBeforeWrite = await stopIfUploadFailed();
|
|
382
|
+
if (failedBeforeWrite) return failedBeforeWrite;
|
|
379
383
|
// Pad index to 6 digits so string-sort order matches numeric order if the
|
|
380
384
|
// finalize path ever sorts lexically. (finalize also parses back to a number.)
|
|
381
385
|
const paddedIndex = String(index).padStart(6, "0");
|
|
@@ -409,15 +413,25 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
409
413
|
|
|
410
414
|
// Update upload progress (best-effort). If total is unknown we treat it as
|
|
411
415
|
// indeterminate and keep progress at its last known value.
|
|
416
|
+
// Chunks may arrive out of order when uploaded in parallel, so take the
|
|
417
|
+
// max of the current persisted value and the incoming index to keep
|
|
418
|
+
// progress monotonically non-decreasing.
|
|
412
419
|
if (total > 0) {
|
|
413
420
|
const failedResponse = await stopIfUploadFailed();
|
|
414
421
|
if (failedResponse) return failedResponse;
|
|
415
|
-
const
|
|
422
|
+
const chunksReceived = Math.max(
|
|
423
|
+
stateNumber(uploadState, "chunksReceived") ?? 0,
|
|
424
|
+
index + 1,
|
|
425
|
+
);
|
|
426
|
+
const progress = Math.max(
|
|
427
|
+
stateNumber(uploadState, "progress") ?? 0,
|
|
428
|
+
Math.min(100, Math.round((chunksReceived / total) * 100)),
|
|
429
|
+
);
|
|
416
430
|
await writeAppState(`recording-upload-${recordingId}`, {
|
|
417
431
|
recordingId,
|
|
418
432
|
status: isFinal ? "processing" : "uploading",
|
|
419
433
|
progress,
|
|
420
|
-
chunksReceived
|
|
434
|
+
chunksReceived,
|
|
421
435
|
totalChunks: total,
|
|
422
436
|
...(expectedDataChunks !== undefined
|
|
423
437
|
? {
|
|
@@ -449,7 +463,10 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
449
463
|
await writeAppState(`recording-upload-${recordingId}`, {
|
|
450
464
|
recordingId,
|
|
451
465
|
status: isFinal ? "processing" : "uploading",
|
|
452
|
-
chunksReceived:
|
|
466
|
+
chunksReceived: Math.max(
|
|
467
|
+
stateNumber(uploadState, "chunksReceived") ?? 0,
|
|
468
|
+
index + 1,
|
|
469
|
+
),
|
|
453
470
|
...(expectedDataChunks !== undefined
|
|
454
471
|
? {
|
|
455
472
|
expectedDataChunks,
|
|
@@ -26,8 +26,26 @@ const DEFAULT_READY_RECOVERY_TIMEOUT_MS = 90_000;
|
|
|
26
26
|
const DEFAULT_READY_RECOVERY_INTERVAL_MS = 3_000;
|
|
27
27
|
const DEFAULT_READY_RECOVERY_FETCH_TIMEOUT_MS = 2_000;
|
|
28
28
|
|
|
29
|
-
function sleep(ms: number): Promise<void> {
|
|
30
|
-
return new Promise((resolve) =>
|
|
29
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
if (signal?.aborted) {
|
|
32
|
+
reject(
|
|
33
|
+
signal.reason instanceof Error ? signal.reason : new Error("Aborted"),
|
|
34
|
+
);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const timer = setTimeout(resolve, ms);
|
|
38
|
+
signal?.addEventListener(
|
|
39
|
+
"abort",
|
|
40
|
+
() => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
reject(
|
|
43
|
+
signal.reason instanceof Error ? signal.reason : new Error("Aborted"),
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
{ once: true },
|
|
47
|
+
);
|
|
48
|
+
});
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
function absoluteUploadUrl(uploadUrl: string): URL {
|
|
@@ -157,9 +175,10 @@ export async function waitForReadyRecordingAfterFinalizeError(args: {
|
|
|
157
175
|
timeoutMs?: number;
|
|
158
176
|
intervalMs?: number;
|
|
159
177
|
fetchTimeoutMs?: number;
|
|
178
|
+
signal?: AbortSignal;
|
|
160
179
|
}): Promise<RecoveredReadyRecording | null> {
|
|
161
180
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
162
|
-
const sleepImpl = args.sleepImpl ?? sleep;
|
|
181
|
+
const sleepImpl = args.sleepImpl ?? ((ms) => sleep(ms, args.signal));
|
|
163
182
|
const timeoutMs = Math.max(
|
|
164
183
|
1,
|
|
165
184
|
args.timeoutMs ?? DEFAULT_READY_RECOVERY_TIMEOUT_MS,
|
|
@@ -182,6 +201,7 @@ export async function waitForReadyRecordingAfterFinalizeError(args: {
|
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
204
|
+
if (args.signal?.aborted) return null;
|
|
185
205
|
const urls = authenticatedUrl
|
|
186
206
|
? [
|
|
187
207
|
{ url: authenticatedUrl, authenticated: true },
|