@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
|
@@ -75,6 +75,30 @@ function normalizedBuilderBodyProse(content: string | null | undefined) {
|
|
|
75
75
|
.trim();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function isEffectivelyEmptyDocumentContent(
|
|
79
|
+
content: string | null | undefined,
|
|
80
|
+
) {
|
|
81
|
+
const normalized = (content ?? "").trim();
|
|
82
|
+
return normalized === "" || normalized === "<empty-block/>";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function shouldRejectStaleEmptyBodySave(args: {
|
|
86
|
+
incomingContent: string | null | undefined;
|
|
87
|
+
currentContent: string | null | undefined;
|
|
88
|
+
loadedUpdatedAt: string | null | undefined;
|
|
89
|
+
currentUpdatedAt: string | null | undefined;
|
|
90
|
+
loadedContentWasEmpty?: boolean | null | undefined;
|
|
91
|
+
}) {
|
|
92
|
+
if (!args.loadedUpdatedAt || !args.currentUpdatedAt) return false;
|
|
93
|
+
if (!isEffectivelyEmptyDocumentContent(args.incomingContent)) return false;
|
|
94
|
+
if (isEffectivelyEmptyDocumentContent(args.currentContent)) return false;
|
|
95
|
+
if (args.loadedContentWasEmpty === true) return true;
|
|
96
|
+
return (
|
|
97
|
+
new Date(args.currentUpdatedAt).getTime() >
|
|
98
|
+
new Date(args.loadedUpdatedAt).getTime()
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
78
102
|
/**
|
|
79
103
|
* Best-effort quote of the first changed span between two document bodies, used
|
|
80
104
|
* as the `{ kind: "text", quote }` descriptor so the recent-edit highlight lands
|
|
@@ -133,6 +157,14 @@ export default defineAction({
|
|
|
133
157
|
.boolean()
|
|
134
158
|
.optional()
|
|
135
159
|
.describe("Favorite status (true/false)"),
|
|
160
|
+
loadedUpdatedAt: z
|
|
161
|
+
.string()
|
|
162
|
+
.optional()
|
|
163
|
+
.describe("Document updatedAt value the client loaded before editing"),
|
|
164
|
+
loadedContentWasEmpty: z
|
|
165
|
+
.boolean()
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Whether the client-loaded content snapshot was empty"),
|
|
136
168
|
// Optional optimistic-concurrency guard for content saves: the
|
|
137
169
|
// `updatedAt` of the document snapshot the caller last loaded/reconciled.
|
|
138
170
|
// When provided alongside `content`, the write is a compare-and-swap on
|
|
@@ -229,6 +261,17 @@ export default defineAction({
|
|
|
229
261
|
content = existing.content;
|
|
230
262
|
}
|
|
231
263
|
}
|
|
264
|
+
if (
|
|
265
|
+
shouldRejectStaleEmptyBodySave({
|
|
266
|
+
incomingContent: content,
|
|
267
|
+
currentContent: existing.content,
|
|
268
|
+
loadedUpdatedAt: args.loadedUpdatedAt,
|
|
269
|
+
currentUpdatedAt: existing.updatedAt,
|
|
270
|
+
loadedContentWasEmpty: args.loadedContentWasEmpty,
|
|
271
|
+
})
|
|
272
|
+
) {
|
|
273
|
+
content = existing.content;
|
|
274
|
+
}
|
|
232
275
|
}
|
|
233
276
|
|
|
234
277
|
// Detect actual changes — a no-op call (e.g. the editor echoing back the
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IconLoader2 } from "@tabler/icons-react";
|
|
2
|
+
|
|
3
|
+
export function BuilderBodySyncingNotice({
|
|
4
|
+
title,
|
|
5
|
+
description,
|
|
6
|
+
}: {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="rounded-lg border border-dashed border-border bg-muted/35 px-4 py-5 text-sm">
|
|
12
|
+
<div className="flex items-center gap-2 font-medium text-foreground">
|
|
13
|
+
<IconLoader2 className="size-4 animate-spin text-muted-foreground" />
|
|
14
|
+
{title}
|
|
15
|
+
</div>
|
|
16
|
+
<p className="mt-2 max-w-2xl leading-6 text-muted-foreground">
|
|
17
|
+
{description}
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type CollabUser,
|
|
12
12
|
} from "@agent-native/core/client";
|
|
13
13
|
import type { Document, DocumentSyncStatus } from "@shared/api";
|
|
14
|
-
import { IconArrowLeft, IconDatabase } from "@tabler/icons-react";
|
|
14
|
+
import { IconArrowLeft, IconDatabase, IconLoader2 } from "@tabler/icons-react";
|
|
15
15
|
import { IconLock } from "@tabler/icons-react";
|
|
16
16
|
import { useQueryClient } from "@tanstack/react-query";
|
|
17
17
|
import {
|
|
@@ -54,6 +54,11 @@ import {
|
|
|
54
54
|
} from "@/lib/local-content-source-files";
|
|
55
55
|
import { cn } from "@/lib/utils";
|
|
56
56
|
|
|
57
|
+
import {
|
|
58
|
+
documentBodyHydrationIsPending,
|
|
59
|
+
isEffectivelyEmptyDocumentContent,
|
|
60
|
+
} from "./body-hydration";
|
|
61
|
+
import { BuilderBodySyncingNotice } from "./BuilderBodySyncingNotice";
|
|
57
62
|
import type { CommentTextAnchor } from "./comment-anchors";
|
|
58
63
|
import { CommentsSidebar } from "./CommentsSidebar";
|
|
59
64
|
import { DocumentBlockFields } from "./DocumentBlockFields";
|
|
@@ -309,7 +314,6 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
309
314
|
);
|
|
310
315
|
const canEdit = document.canEdit ?? true;
|
|
311
316
|
const canEditRef = useRef(canEdit);
|
|
312
|
-
canEditRef.current = canEdit;
|
|
313
317
|
// The block render context (asset/upload resolvers, inline markdown reader,
|
|
314
318
|
// panel popover) is stable for the editor's lifetime. Created once here and
|
|
315
319
|
// provided alongside the content block registry so every registry block in the
|
|
@@ -468,6 +472,10 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
468
472
|
requestSource: TAB_ID,
|
|
469
473
|
user: currentUser,
|
|
470
474
|
});
|
|
475
|
+
const bodyHydrationPending = documentBodyHydrationIsPending(document);
|
|
476
|
+
const editorCanEdit =
|
|
477
|
+
canEdit && !bodyHydrationPending && (isLocalFileDocument || !collabLoading);
|
|
478
|
+
canEditRef.current = editorCanEdit;
|
|
471
479
|
|
|
472
480
|
// Initialize from fetched document, reset on document switch
|
|
473
481
|
useEffect(() => {
|
|
@@ -601,7 +609,14 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
601
609
|
const serverContent = document.content;
|
|
602
610
|
const lastSaved = lastSavedContentRef.current;
|
|
603
611
|
if (serverContent === lastSaved.content) return;
|
|
604
|
-
const
|
|
612
|
+
const staleEmptyLocalOverFreshServer =
|
|
613
|
+
isEffectivelyEmptyDocumentContent(lastSaved.content) &&
|
|
614
|
+
isEffectivelyEmptyDocumentContent(localContent) &&
|
|
615
|
+
!isEffectivelyEmptyDocumentContent(serverContent);
|
|
616
|
+
const adopt =
|
|
617
|
+
localContent === lastSaved.content ||
|
|
618
|
+
contentExternalIsNewer ||
|
|
619
|
+
staleEmptyLocalOverFreshServer;
|
|
605
620
|
if (adopt) {
|
|
606
621
|
if (saveTimeoutRef.current) {
|
|
607
622
|
clearTimeout(saveTimeoutRef.current);
|
|
@@ -701,6 +716,13 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
701
716
|
: undefined;
|
|
702
717
|
return await updateDocument.mutateAsync({
|
|
703
718
|
id: documentId,
|
|
719
|
+
loadedUpdatedAt: documentUpdatedAtRef.current ?? undefined,
|
|
720
|
+
loadedContentWasEmpty:
|
|
721
|
+
updates.content !== undefined
|
|
722
|
+
? isEffectivelyEmptyDocumentContent(
|
|
723
|
+
lastSavedContentRef.current.content,
|
|
724
|
+
)
|
|
725
|
+
: undefined,
|
|
704
726
|
...updates,
|
|
705
727
|
...(baseUpdatedAt !== undefined ? { baseUpdatedAt } : {}),
|
|
706
728
|
});
|
|
@@ -997,7 +1019,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
997
1019
|
// key we force an immediate (non-debounced) save of the current editor
|
|
998
1020
|
// state, then delete the key so `pull-document` knows the flush landed.
|
|
999
1021
|
useEffect(() => {
|
|
1000
|
-
if (!
|
|
1022
|
+
if (!editorCanEdit || isLocalFileDocument) return;
|
|
1001
1023
|
let active = true;
|
|
1002
1024
|
const flushKey = `flush-request-${documentId}`;
|
|
1003
1025
|
const flushPath = agentNativePath(
|
|
@@ -1060,29 +1082,29 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1060
1082
|
active = false;
|
|
1061
1083
|
clearTimeout(timer);
|
|
1062
1084
|
};
|
|
1063
|
-
}, [
|
|
1085
|
+
}, [documentId, editorCanEdit, isLocalFileDocument, persistDocumentUpdates]);
|
|
1064
1086
|
|
|
1065
1087
|
const handleTitleChange = useCallback(
|
|
1066
1088
|
(newTitle: string) => {
|
|
1067
|
-
if (!
|
|
1089
|
+
if (!editorCanEdit) return;
|
|
1068
1090
|
setLocalTitle(newTitle);
|
|
1069
1091
|
debouncedSave(newTitle, localContentRef.current);
|
|
1070
1092
|
},
|
|
1071
|
-
[
|
|
1093
|
+
[debouncedSave, editorCanEdit],
|
|
1072
1094
|
);
|
|
1073
1095
|
|
|
1074
1096
|
const handleContentChange = useCallback(
|
|
1075
1097
|
(newContent: string) => {
|
|
1076
|
-
if (!
|
|
1098
|
+
if (!editorCanEdit) return;
|
|
1077
1099
|
setLocalContent(newContent);
|
|
1078
1100
|
debouncedSave(localTitleRef.current, newContent);
|
|
1079
1101
|
},
|
|
1080
|
-
[
|
|
1102
|
+
[debouncedSave, editorCanEdit],
|
|
1081
1103
|
);
|
|
1082
1104
|
|
|
1083
1105
|
const handleContentSaveNow = useCallback(
|
|
1084
1106
|
async (newContent: string) => {
|
|
1085
|
-
if (!
|
|
1107
|
+
if (!editorCanEdit) return false;
|
|
1086
1108
|
if (saveTimeoutRef.current) {
|
|
1087
1109
|
clearTimeout(saveTimeoutRef.current);
|
|
1088
1110
|
saveTimeoutRef.current = null;
|
|
@@ -1096,7 +1118,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1096
1118
|
);
|
|
1097
1119
|
return result.contentPersisted;
|
|
1098
1120
|
},
|
|
1099
|
-
[
|
|
1121
|
+
[editorCanEdit, saveDocumentImmediately],
|
|
1100
1122
|
);
|
|
1101
1123
|
|
|
1102
1124
|
// Comments state — pending comment from text selection
|
|
@@ -1114,11 +1136,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1114
1136
|
);
|
|
1115
1137
|
const hasComments =
|
|
1116
1138
|
!isLocalFileDocument &&
|
|
1117
|
-
|
|
1139
|
+
editorCanEdit &&
|
|
1118
1140
|
((threads?.length ?? 0) > 0 || !!pendingComment);
|
|
1119
1141
|
const hasCommentRailSpace = useMinViewportWidth(1024);
|
|
1120
1142
|
const showDesktopComments = hasComments && hasCommentRailSpace;
|
|
1121
|
-
const showCommentsSheet =
|
|
1143
|
+
const showCommentsSheet =
|
|
1144
|
+
hasComments && editorCanEdit && !showDesktopComments;
|
|
1122
1145
|
|
|
1123
1146
|
const handleComment = useCallback(
|
|
1124
1147
|
(
|
|
@@ -1175,7 +1198,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1175
1198
|
|
|
1176
1199
|
const handleTitlePaste = useCallback(
|
|
1177
1200
|
(event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1178
|
-
if (!
|
|
1201
|
+
if (!editorCanEdit) return;
|
|
1179
1202
|
|
|
1180
1203
|
const pastedText = event.clipboardData.getData("text/plain");
|
|
1181
1204
|
if (!pastedText) return;
|
|
@@ -1196,12 +1219,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1196
1219
|
titleInputRef.current?.setSelectionRange(nextCaret, nextCaret);
|
|
1197
1220
|
});
|
|
1198
1221
|
},
|
|
1199
|
-
[
|
|
1222
|
+
[editorCanEdit, handleTitleChange, localTitle],
|
|
1200
1223
|
);
|
|
1201
1224
|
|
|
1202
1225
|
// Auto-focus title on new empty documents once collab finishes loading
|
|
1203
1226
|
useEffect(() => {
|
|
1204
|
-
if (
|
|
1227
|
+
if (editorCanEdit && shouldFocusTitleRef.current) {
|
|
1205
1228
|
shouldFocusTitleRef.current = false;
|
|
1206
1229
|
requestAnimationFrame(() => titleInputRef.current?.focus());
|
|
1207
1230
|
}
|
|
@@ -1212,10 +1235,6 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1212
1235
|
[document, documents],
|
|
1213
1236
|
);
|
|
1214
1237
|
|
|
1215
|
-
if (!isLocalFileDocument && collabLoading) {
|
|
1216
|
-
return <DocumentEditorSkeleton />;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
1238
|
const sidebar = (
|
|
1220
1239
|
<CommentsSidebar
|
|
1221
1240
|
documentId={documentId}
|
|
@@ -1315,7 +1334,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1315
1334
|
/>
|
|
1316
1335
|
{document.icon || !isDatabasePage ? (
|
|
1317
1336
|
<div className="mb-1">
|
|
1318
|
-
{
|
|
1337
|
+
{editorCanEdit ? (
|
|
1319
1338
|
<EmojiPicker
|
|
1320
1339
|
icon={document.icon}
|
|
1321
1340
|
defaultIcon={defaultIcon}
|
|
@@ -1376,6 +1395,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1376
1395
|
titleFocusedRef.current = false;
|
|
1377
1396
|
}}
|
|
1378
1397
|
onKeyDown={(e) => {
|
|
1398
|
+
if (!editorCanEdit) return;
|
|
1379
1399
|
if (e.key === "Enter") {
|
|
1380
1400
|
e.preventDefault();
|
|
1381
1401
|
const pm = window.document.querySelector(
|
|
@@ -1386,7 +1406,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1386
1406
|
}}
|
|
1387
1407
|
aria-label={t("editor.documentTitle")}
|
|
1388
1408
|
placeholder={t("editor.title")}
|
|
1389
|
-
readOnly={!
|
|
1409
|
+
readOnly={!editorCanEdit}
|
|
1390
1410
|
style={{ fieldSizing: "content" } as any}
|
|
1391
1411
|
className={cn(
|
|
1392
1412
|
"block w-full resize-none overflow-hidden break-words border-none bg-transparent p-0 font-bold leading-tight text-foreground outline-none placeholder:text-muted-foreground/40",
|
|
@@ -1396,7 +1416,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1396
1416
|
{document.databaseMembership && !isLocalFileDocument ? (
|
|
1397
1417
|
<DocumentProperties
|
|
1398
1418
|
documentId={documentId}
|
|
1399
|
-
canEdit={
|
|
1419
|
+
canEdit={editorCanEdit}
|
|
1400
1420
|
/>
|
|
1401
1421
|
) : null}
|
|
1402
1422
|
</div>
|
|
@@ -1418,6 +1438,17 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1418
1438
|
}}
|
|
1419
1439
|
>
|
|
1420
1440
|
{(() => {
|
|
1441
|
+
if (bodyHydrationPending) {
|
|
1442
|
+
return (
|
|
1443
|
+
<BuilderBodySyncingNotice
|
|
1444
|
+
title={t("editor.builderBodySyncing")}
|
|
1445
|
+
description={t(
|
|
1446
|
+
"editor.builderBodySyncingDescription",
|
|
1447
|
+
)}
|
|
1448
|
+
/>
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1421
1452
|
// The primary "Content" Blocks field IS the document body,
|
|
1422
1453
|
// with the full collaborative editor. It renders chromeless
|
|
1423
1454
|
// when it's the only Blocks field, or inside a
|
|
@@ -1425,7 +1456,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1425
1456
|
// fields.
|
|
1426
1457
|
const primaryEditor = (
|
|
1427
1458
|
<VisualEditor
|
|
1428
|
-
key={documentId}
|
|
1459
|
+
key={`${documentId}:${editorCanEdit && !isLocalFileDocument ? "live" : "snapshot"}`}
|
|
1429
1460
|
documentId={documentId}
|
|
1430
1461
|
content={
|
|
1431
1462
|
isLocalFileDocument ? localContent : document.content
|
|
@@ -1445,13 +1476,13 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1445
1476
|
collabSynced={collabEnabled ? collabSynced : true}
|
|
1446
1477
|
awareness={collabEnabled ? awareness : null}
|
|
1447
1478
|
user={currentUser}
|
|
1448
|
-
editable={
|
|
1479
|
+
editable={editorCanEdit}
|
|
1449
1480
|
localFileMode={isLocalFileDocument}
|
|
1450
1481
|
localFilePath={
|
|
1451
1482
|
isLocalFileDocument ? document.source?.path : null
|
|
1452
1483
|
}
|
|
1453
1484
|
onComment={
|
|
1454
|
-
|
|
1485
|
+
editorCanEdit && !isLocalFileDocument
|
|
1455
1486
|
? handleComment
|
|
1456
1487
|
: undefined
|
|
1457
1488
|
}
|
|
@@ -1459,7 +1490,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1459
1490
|
activeThreadId={activeThreadId}
|
|
1460
1491
|
pendingHighlight={pendingComment?.range ?? null}
|
|
1461
1492
|
onActivateThread={
|
|
1462
|
-
|
|
1493
|
+
editorCanEdit && !isLocalFileDocument
|
|
1463
1494
|
? setSelectedThreadId
|
|
1464
1495
|
: undefined
|
|
1465
1496
|
}
|
|
@@ -1476,7 +1507,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1476
1507
|
return (
|
|
1477
1508
|
<DocumentBlockFields
|
|
1478
1509
|
documentId={documentId}
|
|
1479
|
-
canEdit={
|
|
1510
|
+
canEdit={editorCanEdit}
|
|
1480
1511
|
primaryEditor={primaryEditor}
|
|
1481
1512
|
/>
|
|
1482
1513
|
);
|
|
@@ -1484,6 +1515,17 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1484
1515
|
|
|
1485
1516
|
return primaryEditor;
|
|
1486
1517
|
})()}
|
|
1518
|
+
{!bodyHydrationPending &&
|
|
1519
|
+
!isLocalFileDocument &&
|
|
1520
|
+
collabLoading ? (
|
|
1521
|
+
<div
|
|
1522
|
+
className="mt-4 inline-flex items-center gap-2 rounded-md border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground"
|
|
1523
|
+
role="status"
|
|
1524
|
+
>
|
|
1525
|
+
<IconLoader2 className="size-3.5 animate-spin" />
|
|
1526
|
+
{t("editor.collabConnectingReadOnly")}
|
|
1527
|
+
</div>
|
|
1528
|
+
) : null}
|
|
1487
1529
|
</div>
|
|
1488
1530
|
</div>
|
|
1489
1531
|
|
|
@@ -2621,6 +2621,17 @@ export function AddProperty({
|
|
|
2621
2621
|
}))
|
|
2622
2622
|
.filter((group) => group.fields.length > 0);
|
|
2623
2623
|
const addPropertySearchInputRef = useRef<HTMLInputElement>(null);
|
|
2624
|
+
const [pendingPropertyType, setPendingPropertyType] =
|
|
2625
|
+
useState<DocumentPropertyType | null>(null);
|
|
2626
|
+
const [pendingSourceFieldId, setPendingSourceFieldId] = useState<
|
|
2627
|
+
string | null
|
|
2628
|
+
>(null);
|
|
2629
|
+
const [addPropertyError, setAddPropertyError] = useState<string | null>(null);
|
|
2630
|
+
const isAddingProperty =
|
|
2631
|
+
configure.isPending ||
|
|
2632
|
+
addSourceFieldProperty.isPending ||
|
|
2633
|
+
pendingPropertyType !== null ||
|
|
2634
|
+
pendingSourceFieldId !== null;
|
|
2624
2635
|
|
|
2625
2636
|
useEffect(() => {
|
|
2626
2637
|
if (!open) return;
|
|
@@ -2632,27 +2643,49 @@ export function AddProperty({
|
|
|
2632
2643
|
}, [open]);
|
|
2633
2644
|
|
|
2634
2645
|
function closeAddPropertyPicker() {
|
|
2646
|
+
if (isAddingProperty) return;
|
|
2635
2647
|
setTypeQuery("");
|
|
2648
|
+
setAddPropertyError(null);
|
|
2636
2649
|
setOpen(false);
|
|
2637
2650
|
}
|
|
2638
2651
|
|
|
2639
2652
|
async function add(type: DocumentPropertyType) {
|
|
2640
2653
|
const label = t(`editor.propertyTypes.${type}`);
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2654
|
+
setPendingPropertyType(type);
|
|
2655
|
+
setPendingSourceFieldId(null);
|
|
2656
|
+
setAddPropertyError(null);
|
|
2657
|
+
try {
|
|
2658
|
+
await configure.mutateAsync({
|
|
2659
|
+
documentId,
|
|
2660
|
+
name: label,
|
|
2661
|
+
type,
|
|
2662
|
+
options: defaultPropertyOptions(type),
|
|
2663
|
+
});
|
|
2664
|
+
setTypeQuery("");
|
|
2665
|
+
setOpen(false);
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
setAddPropertyError(error instanceof Error ? error.message : "");
|
|
2668
|
+
} finally {
|
|
2669
|
+
setPendingPropertyType(null);
|
|
2670
|
+
}
|
|
2648
2671
|
}
|
|
2649
2672
|
|
|
2650
2673
|
async function addFromSourceField(sourceFieldId: string) {
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2674
|
+
setPendingSourceFieldId(sourceFieldId);
|
|
2675
|
+
setPendingPropertyType(null);
|
|
2676
|
+
setAddPropertyError(null);
|
|
2677
|
+
try {
|
|
2678
|
+
await addSourceFieldProperty.mutateAsync({
|
|
2679
|
+
documentId,
|
|
2680
|
+
sourceFieldId,
|
|
2681
|
+
});
|
|
2682
|
+
setTypeQuery("");
|
|
2683
|
+
setOpen(false);
|
|
2684
|
+
} catch (error) {
|
|
2685
|
+
setAddPropertyError(error instanceof Error ? error.message : "");
|
|
2686
|
+
} finally {
|
|
2687
|
+
setPendingSourceFieldId(null);
|
|
2688
|
+
}
|
|
2656
2689
|
}
|
|
2657
2690
|
|
|
2658
2691
|
return (
|
|
@@ -2661,7 +2694,7 @@ export function AddProperty({
|
|
|
2661
2694
|
onOpenChange={(nextOpen) => {
|
|
2662
2695
|
if (nextOpen) {
|
|
2663
2696
|
setOpen(true);
|
|
2664
|
-
} else {
|
|
2697
|
+
} else if (!isAddingProperty) {
|
|
2665
2698
|
closeAddPropertyPicker();
|
|
2666
2699
|
}
|
|
2667
2700
|
}}
|
|
@@ -2729,11 +2762,16 @@ export function AddProperty({
|
|
|
2729
2762
|
aria-label={t("editor.properties.sourceField", {
|
|
2730
2763
|
name: field.sourceFieldLabel,
|
|
2731
2764
|
})}
|
|
2732
|
-
disabled={
|
|
2765
|
+
disabled={isAddingProperty}
|
|
2766
|
+
aria-busy={pendingSourceFieldId === field.id}
|
|
2733
2767
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent disabled:opacity-50"
|
|
2734
2768
|
onClick={() => void addFromSourceField(field.id)}
|
|
2735
2769
|
>
|
|
2736
|
-
|
|
2770
|
+
{pendingSourceFieldId === field.id ? (
|
|
2771
|
+
<Spinner className="size-4 shrink-0 text-muted-foreground" />
|
|
2772
|
+
) : (
|
|
2773
|
+
<IconLink className="size-4 shrink-0 text-muted-foreground" />
|
|
2774
|
+
)}
|
|
2737
2775
|
<span className="min-w-0 flex-1 truncate">
|
|
2738
2776
|
{field.sourceFieldLabel}
|
|
2739
2777
|
</span>
|
|
@@ -2761,10 +2799,15 @@ export function AddProperty({
|
|
|
2761
2799
|
type: t(`editor.propertyTypes.${type}`),
|
|
2762
2800
|
})}
|
|
2763
2801
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent"
|
|
2764
|
-
disabled={
|
|
2802
|
+
disabled={isAddingProperty}
|
|
2803
|
+
aria-busy={pendingPropertyType === type}
|
|
2765
2804
|
onClick={() => void add(type)}
|
|
2766
2805
|
>
|
|
2767
|
-
|
|
2806
|
+
{pendingPropertyType === type ? (
|
|
2807
|
+
<Spinner className="size-4 text-muted-foreground" />
|
|
2808
|
+
) : (
|
|
2809
|
+
<Icon className="size-4 text-muted-foreground" />
|
|
2810
|
+
)}
|
|
2768
2811
|
<span className="flex-1">
|
|
2769
2812
|
{t(`editor.propertyTypes.${type}`)}
|
|
2770
2813
|
</span>
|
|
@@ -2776,6 +2819,15 @@ export function AddProperty({
|
|
|
2776
2819
|
</button>
|
|
2777
2820
|
);
|
|
2778
2821
|
})}
|
|
2822
|
+
{addPropertyError !== null ? (
|
|
2823
|
+
<div
|
|
2824
|
+
role="alert"
|
|
2825
|
+
className="px-2 py-1.5 text-xs text-destructive"
|
|
2826
|
+
>
|
|
2827
|
+
{t("editor.properties.addPropertyFailed")}
|
|
2828
|
+
{addPropertyError ? ` ${addPropertyError}` : null}
|
|
2829
|
+
</div>
|
|
2830
|
+
) : null}
|
|
2779
2831
|
</div>
|
|
2780
2832
|
</div>
|
|
2781
2833
|
</PopoverContent>
|
|
@@ -326,7 +326,7 @@ const JoinFirstBodyBlockToTitle = Extension.create<{
|
|
|
326
326
|
|
|
327
327
|
const text = firstBlock.textContent.trim();
|
|
328
328
|
if (!text) {
|
|
329
|
-
|
|
329
|
+
setTimeout(() => this.options.onJoinTitle?.(""), 0);
|
|
330
330
|
return true;
|
|
331
331
|
}
|
|
332
332
|
|
|
@@ -336,7 +336,7 @@ const JoinFirstBodyBlockToTitle = Extension.create<{
|
|
|
336
336
|
? state.tr.replaceWith(0, firstBlock.nodeSize, paragraph.create())
|
|
337
337
|
: state.tr.delete(0, firstBlock.nodeSize);
|
|
338
338
|
view.dispatch(tr.scrollIntoView());
|
|
339
|
-
|
|
339
|
+
setTimeout(() => this.options.onJoinTitle?.(text), 0);
|
|
340
340
|
return true;
|
|
341
341
|
};
|
|
342
342
|
|
|
@@ -1731,7 +1731,7 @@ export function VisualEditor({
|
|
|
1731
1731
|
// clobber DB content with an empty string). `registerEmitted` records
|
|
1732
1732
|
// this as the last-emitted value and returns false to skip the save.
|
|
1733
1733
|
if (!guards.registerEmitted(normalized)) return true;
|
|
1734
|
-
|
|
1734
|
+
setTimeout(() => onChangeRef.current(normalized), 0);
|
|
1735
1735
|
return true;
|
|
1736
1736
|
} catch (err: any) {
|
|
1737
1737
|
toast.error(
|
|
@@ -2172,7 +2172,7 @@ export function VisualEditor({
|
|
|
2172
2172
|
) as HTMLElement | null;
|
|
2173
2173
|
if (!el) return;
|
|
2174
2174
|
const id = el.getAttribute("data-comment-thread");
|
|
2175
|
-
if (id) onActivateThread(id);
|
|
2175
|
+
if (id) setTimeout(() => onActivateThread(id), 0);
|
|
2176
2176
|
};
|
|
2177
2177
|
dom.addEventListener("click", handleClick);
|
|
2178
2178
|
return () => dom.removeEventListener("click", handleClick);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContentDatabaseBodyHydration,
|
|
3
|
+
ContentDatabaseBodyHydrationSummary,
|
|
4
|
+
ContentDatabaseItem,
|
|
5
|
+
Document,
|
|
6
|
+
} from "@shared/api";
|
|
7
|
+
|
|
8
|
+
export function builderBodyHydrationIsPending(
|
|
9
|
+
hydration: ContentDatabaseBodyHydration | null | undefined,
|
|
10
|
+
) {
|
|
11
|
+
return (
|
|
12
|
+
!!hydration &&
|
|
13
|
+
(hydration.status === "pending" || hydration.status === "hydrating")
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function builderBodyHydrationIsTerminalError(
|
|
18
|
+
hydration: ContentDatabaseBodyHydration | null | undefined,
|
|
19
|
+
) {
|
|
20
|
+
return hydration?.status === "error";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function builderBodyHydrationDisplayHydratedCount(args: {
|
|
24
|
+
summary: ContentDatabaseBodyHydrationSummary;
|
|
25
|
+
highWaterCount?: number | null;
|
|
26
|
+
}) {
|
|
27
|
+
const total = Math.max(0, args.summary.total);
|
|
28
|
+
const unresolved = Math.max(
|
|
29
|
+
0,
|
|
30
|
+
args.summary.pending + args.summary.hydrating + args.summary.error,
|
|
31
|
+
);
|
|
32
|
+
const maxResolved = Math.max(0, total - unresolved);
|
|
33
|
+
return Math.min(
|
|
34
|
+
Math.max(args.summary.hydrated, args.highWaterCount ?? 0),
|
|
35
|
+
unresolved > 0 ? maxResolved : total,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function databaseItemBodyHydrationIsPending(
|
|
40
|
+
item: Pick<ContentDatabaseItem, "bodyHydration" | "document">,
|
|
41
|
+
) {
|
|
42
|
+
if (
|
|
43
|
+
item.document.databaseMembership?.sourceId &&
|
|
44
|
+
!item.bodyHydration &&
|
|
45
|
+
!item.document.databaseMembership.bodyHydration
|
|
46
|
+
) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return builderBodyHydrationIsPending(
|
|
50
|
+
item.bodyHydration ?? item.document.databaseMembership?.bodyHydration,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function documentBodyHydrationIsPending(
|
|
55
|
+
document: Pick<Document, "databaseMembership">,
|
|
56
|
+
) {
|
|
57
|
+
return builderBodyHydrationIsPending(
|
|
58
|
+
document.databaseMembership?.bodyHydration,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function previewBodyHydrationIsPending(args: {
|
|
63
|
+
item: Pick<ContentDatabaseItem, "bodyHydration" | "document">;
|
|
64
|
+
document: Pick<Document, "databaseMembership"> | null | undefined;
|
|
65
|
+
}) {
|
|
66
|
+
const membership =
|
|
67
|
+
args.document?.databaseMembership ?? args.item.document.databaseMembership;
|
|
68
|
+
if (
|
|
69
|
+
membership?.sourceId &&
|
|
70
|
+
!args.document &&
|
|
71
|
+
!args.item.bodyHydration &&
|
|
72
|
+
!args.item.document.databaseMembership?.bodyHydration
|
|
73
|
+
) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
databaseItemBodyHydrationIsPending(args.item) ||
|
|
78
|
+
(args.document ? documentBodyHydrationIsPending(args.document) : false)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function previewBodyHydrationIsTerminalError(args: {
|
|
83
|
+
item: Pick<ContentDatabaseItem, "bodyHydration" | "document">;
|
|
84
|
+
document: Pick<Document, "databaseMembership"> | null | undefined;
|
|
85
|
+
}) {
|
|
86
|
+
return (
|
|
87
|
+
builderBodyHydrationIsTerminalError(
|
|
88
|
+
args.document?.databaseMembership?.bodyHydration,
|
|
89
|
+
) ||
|
|
90
|
+
builderBodyHydrationIsTerminalError(
|
|
91
|
+
args.item.bodyHydration ??
|
|
92
|
+
args.item.document.databaseMembership?.bodyHydration,
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function isEffectivelyEmptyDocumentContent(
|
|
98
|
+
content: string | null | undefined,
|
|
99
|
+
) {
|
|
100
|
+
const normalized = (content ?? "").trim();
|
|
101
|
+
return normalized === "" || normalized === "<empty-block/>";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function shouldIgnorePreviewEmptyNormalization(args: {
|
|
105
|
+
currentContent: string | null | undefined;
|
|
106
|
+
nextContent: string | null | undefined;
|
|
107
|
+
}) {
|
|
108
|
+
return (
|
|
109
|
+
isEffectivelyEmptyDocumentContent(args.currentContent) &&
|
|
110
|
+
isEffectivelyEmptyDocumentContent(args.nextContent)
|
|
111
|
+
);
|
|
112
|
+
}
|