@allbluecn/web-app 0.22.4 → 0.23.1
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/package.json +4 -14
- package/src/__tests__/integration/login-email-acceptance.test.ts +16 -0
- package/src/__tests__/story-view.test.ts +33 -1
- package/src/components/agents/__tests__/agent-bindings-tab.test.tsx +221 -0
- package/src/components/agents/__tests__/agents-page.test.tsx +284 -0
- package/src/components/agents/agent-bindings-tab.tsx +59 -45
- package/src/components/agents/agent-form-dialog.tsx +3 -3
- package/src/components/agents/agent-selector.tsx +2 -3
- package/src/components/agents/agents-page.tsx +66 -31
- package/src/components/agents/slash-skill-menu.tsx +2 -3
- package/src/components/auth/__tests__/session-guard.test.tsx +74 -0
- package/src/components/auth/session-guard.tsx +8 -0
- package/src/components/billing/login-prompt-dialog.tsx +1 -1
- package/src/components/billing/subscription-provider.tsx +2 -16
- package/src/components/bug/__tests__/peek-modes.test.tsx +8 -0
- package/src/components/bug/__tests__/subgroup-header.test.tsx +202 -40
- package/src/components/bug/peek-panel.tsx +5 -3
- package/src/components/bug/views/bug-list/group.tsx +12 -2
- package/src/components/bug/views/bug-list/index.tsx +94 -67
- package/src/components/command/command-palette.tsx +10 -9
- package/src/components/design/canvas/document/frame-custom-presets.ts +9 -1
- package/src/components/design/canvas/panels/toolbar.tsx +21 -22
- package/src/components/design/canvas/react/__tests__/canvas-context-load-echo.test.tsx +199 -0
- package/src/components/design/canvas/react/__tests__/hooks.test.tsx +17 -3
- package/src/components/design/canvas/react/canvas-context.tsx +47 -26
- package/src/components/design/canvas/react/hooks.ts +39 -34
- package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +17 -0
- package/src/components/design/editor/layout/__tests__/context-menu.test.tsx +17 -1
- package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +17 -0
- package/src/components/design/editor/layout/__tests__/persistence-restore.test.tsx +17 -1
- package/src/components/design/editor/layout/__tests__/ssr-localstorage-smoke.test.tsx +17 -1
- package/src/components/design/editor/layout/design-editor-layout.tsx +13 -2
- package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +3 -19
- package/src/components/devtools/devtools-panel.tsx +56 -0
- package/src/components/forms/__tests__/create-app-form.test.tsx +131 -0
- package/src/components/forms/app-field-error.tsx +21 -1
- package/src/components/forms/app-field.tsx +8 -8
- package/src/components/forms/app-submit.tsx +12 -9
- package/src/components/forms/create-app-form.tsx +124 -0
- package/src/components/forms/index.ts +2 -3
- package/src/components/harness/__tests__/gallery-behavior.test.tsx +37 -1
- package/src/components/harness/__tests__/harness-workbench-first-send.test.tsx +4 -1
- package/src/components/harness/__tests__/message-actions.test.tsx +7 -6
- package/src/components/harness/__tests__/use-video-generation.test.tsx +165 -0
- package/src/components/harness/ai-chat-history/index.tsx +5 -3
- package/src/components/harness/gallery/gallery-card.tsx +57 -35
- package/src/components/harness/gallery/gallery-grid.tsx +32 -15
- package/src/components/harness/gallery/gallery-types.ts +19 -0
- package/src/components/harness/gallery/gallery-video-card.tsx +54 -0
- package/src/components/harness/gallery/types.ts +2 -0
- package/src/components/harness/gallery/use-video-generation.ts +95 -0
- package/src/components/harness/harness-workbench-page.tsx +2 -3
- package/src/components/harness/message-actions.tsx +5 -3
- package/src/components/hotkeys/shortcuts-panel.tsx +28 -13
- package/src/components/iteration/__tests__/burndown-chart.test.tsx +118 -0
- package/src/components/iteration/burndown-chart.tsx +54 -23
- package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +96 -0
- package/src/components/knowledge/editor/knowledge-doc-editor.tsx +56 -1
- package/src/components/knowledge/editor/mindmap-doc-editor.tsx +12 -0
- package/src/components/knowledge/editor/use-auto-save-channels.ts +3 -1
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +0 -4
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +11 -11
- package/src/components/layout/sidebar-03/nav-main.tsx +12 -15
- package/src/components/layout/sidebar-03/nav-notifications.tsx +5 -8
- package/src/components/layout/sidebar-03/nav-workspace.tsx +11 -9
- package/src/components/notifications/__tests__/settings-notifications.test.tsx +158 -0
- package/src/components/notifications/settings-notifications.tsx +37 -13
- package/src/components/notifications/team-notifications.tsx +10 -14
- package/src/components/settings/personal-team/account/__tests__/account-client.test.tsx +170 -0
- package/src/components/settings/personal-team/account/account-client.tsx +44 -7
- package/src/components/settings/personal-team/team/team-client.tsx +17 -19
- package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +210 -41
- package/src/components/shared-kit/list-kit/frozen-header.ts +134 -21
- package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +20 -27
- package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +8 -0
- package/src/components/story/__tests__/story-list-table.test.tsx +214 -0
- package/src/components/story/columns.tsx +11 -2
- package/src/components/story/peek-panel.tsx +4 -2
- package/src/components/story/story-list-table.tsx +16 -1
- package/src/components/story/views/story-list/__tests__/frozen-headers.test.tsx +186 -0
- package/src/components/story/views/story-list/group.tsx +12 -1
- package/src/components/story/views/story-list/index.tsx +101 -68
- package/src/components/testcase/__tests__/case-row.test.tsx +134 -2
- package/src/components/testcase/__tests__/peek-panel.test.tsx +8 -0
- package/src/components/testcase/__tests__/step-editor.test.tsx +106 -0
- package/src/components/testcase/list/grouped-list.tsx +50 -22
- package/src/components/testcase/peek-panel.tsx +7 -4
- package/src/components/testcase/step-editor.tsx +196 -150
- package/src/components/tiptap/notion-like/editor-extensions.ts +0 -3
- package/src/components/tiptap/notion-like/notion-like-editor-header.tsx +1 -31
- package/src/components/tiptap/notion-like/notion-like-editor.tsx +32 -1
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +2 -28
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +2 -28
- package/src/components/workspace/dialogs/create-workspace-dialog.tsx +45 -43
- package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +38 -36
- package/src/design-engine/engine/engine-decor.ts +2 -0
- package/src/design-engine/engine/engine-path.ts +2 -0
- package/src/hooks/__tests__/use-unsaved-guard.test.tsx +180 -0
- package/src/hooks/use-notifications.ts +9 -16
- package/src/hooks/use-unsaved-guard.tsx +87 -0
- package/src/lib/__tests__/pacer.test.ts +33 -96
- package/src/lib/__tests__/query-defaults.test.ts +169 -0
- package/src/lib/__tests__/query-global-error.test.ts +256 -0
- package/src/lib/anyhub/anyhub-store.ts +2 -2
- package/src/lib/changelog/sdk-versions.ts +2 -10
- package/src/lib/collab/presence-store.ts +5 -5
- package/src/lib/collab/use-presence-list-refresh.ts +2 -2
- package/src/lib/collab/use-share-presence.ts +2 -2
- package/src/lib/collections/query-keys.ts +7 -2
- package/src/lib/commands/__tests__/command-palette-state.test.ts +12 -14
- package/src/lib/commands/__tests__/registry.test.ts +6 -3
- package/src/lib/commands/command-palette-state.ts +8 -31
- package/src/lib/hotkeys/__tests__/registry.test.ts +71 -18
- package/src/lib/hotkeys/registry.ts +188 -85
- package/src/lib/hotkeys/types.ts +1 -13
- package/src/lib/pacer.ts +1 -79
- package/src/lib/queries/README.md +67 -0
- package/src/lib/queries/__tests__/agents-queries.test.ts +101 -0
- package/src/lib/queries/__tests__/compliance-queries.test.ts +136 -0
- package/src/lib/queries/__tests__/notifications-queries.test.ts +175 -0
- package/src/lib/queries/__tests__/team-queries.test.ts +87 -0
- package/src/lib/queries/agents.ts +40 -0
- package/src/lib/queries/compliance.ts +69 -0
- package/src/lib/queries/notifications.ts +63 -0
- package/src/lib/queries/team.ts +39 -0
- package/src/lib/query-defaults.ts +8 -0
- package/src/lib/query-global-error.ts +128 -0
- package/src/lib/remote-auth-client.ts +16 -0
- package/src/paraglide/README.md +1 -1
- package/src/paraglide/messages/_index.d.ts +11 -0
- package/src/paraglide/messages/_index.js +5659 -88285
- package/src/paraglide/messages/common_error_generic.d.ts +16 -0
- package/src/paraglide/messages/common_error_generic.js +29 -0
- package/src/paraglide/messages/common_unsaved_body.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_body.js +29 -0
- package/src/paraglide/messages/common_unsaved_leave.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_leave.js +29 -0
- package/src/paraglide/messages/common_unsaved_stay.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_stay.js +29 -0
- package/src/paraglide/messages/common_unsaved_title.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_title.js +29 -0
- package/src/paraglide/messages/gallery_videofailed1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videofailed1.js +30 -0
- package/src/paraglide/messages/gallery_videogenerating1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videogenerating1.js +30 -0
- package/src/paraglide/messages/gallery_videomissingprompt2.d.ts +17 -0
- package/src/paraglide/messages/gallery_videomissingprompt2.js +30 -0
- package/src/paraglide/messages/gallery_videoprogress1.d.ts +19 -0
- package/src/paraglide/messages/gallery_videoprogress1.js +30 -0
- package/src/paraglide/messages/gallery_videostop1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videostop1.js +30 -0
- package/src/paraglide/messages/story_table_resizecolumn1.d.ts +17 -0
- package/src/paraglide/messages/story_table_resizecolumn1.js +30 -0
- package/src/providers/index.tsx +5 -50
- package/src/routeTree.gen.ts +21 -0
- package/src/router.tsx +21 -8
- package/src/routes/__root.tsx +2 -1
- package/src/routes/__tests__/-edition-routing.test.tsx +6 -1
- package/src/routes/_login/forgot-password/route.lazy.tsx +7 -6
- package/src/routes/_login/login/route.lazy.tsx +10 -9
- package/src/routes/_login/register/route.lazy.tsx +19 -18
- package/src/routes/_login/reset-password/route.lazy.tsx +10 -9
- package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -12
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +16 -13
- package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +1 -1
- package/src/routes/_nav/notifications/route.tsx +6 -16
- package/src/routes/_nav/settings/-settings-tabs.tsx +9 -12
- package/src/routes/api/generate/__tests__/media.test.ts +378 -0
- package/src/routes/api/generate/media.ts +179 -0
- package/src/server/__tests__/agent-client-graph.test.ts +0 -1
- package/src/server/__tests__/agent-crud.test.ts +37 -4
- package/src/server/__tests__/bug-groups-two-level.test.ts +34 -1
- package/src/server/__tests__/chat-conversations.test.ts +37 -4
- package/src/server/__tests__/chat-list-messages.test.ts +40 -7
- package/src/server/__tests__/csv-export.test.ts +36 -3
- package/src/server/__tests__/desktop-bootstrap.test.ts +189 -0
- package/src/server/__tests__/remote-auth.test.ts +72 -0
- package/src/server/__tests__/skill-crud.test.ts +37 -4
- package/src/server/__tests__/sso-domain.test.ts +1 -1
- package/src/server/__tests__/sso-rate-limit-singleton.test.ts +123 -0
- package/src/server/__tests__/task-serverfn-crud.test.ts +35 -2
- package/src/server/__tests__/task-serverfn-queries.test.ts +35 -2
- package/src/server/__tests__/testcase-board.test.ts +38 -5
- package/src/server/__tests__/testcase-bulk.test.ts +38 -5
- package/src/server/__tests__/testcase-list-scope.test.ts +38 -5
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +126 -2
- package/src/server/ai-gateway/__tests__/chat-stream-builder-code-mode.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-skills.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/pgvector-memory-adapter.test.ts +30 -0
- package/src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts +92 -0
- package/src/server/ai-gateway/__tests__/source-mode.test.ts +49 -8
- package/src/server/ai-gateway/ai-output-guard.ts +50 -2
- package/src/server/ai-gateway/chat-stream-builder.ts +3 -0
- package/src/server/ai-gateway/media/index.ts +36 -29
- package/src/server/ai-gateway/media/trim-input.ts +52 -0
- package/src/server/ai-gateway/memory/pgvector-adapter.ts +27 -13
- package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +252 -41
- package/src/server/ai-gateway/sandbox/harness-sandbox.ts +6 -67
- package/src/server/ai-gateway/source-mode.ts +24 -8
- package/src/server/desktop-bootstrap.ts +74 -0
- package/src/server/lib/__tests__/rate-limit.test.ts +0 -1
- package/src/server/lib/rate-limit.ts +0 -2
- package/src/server/middlewares/__tests__/auth-context.test.ts +156 -0
- package/src/server/middlewares/auth-context.ts +62 -0
- package/src/server/remote-auth.ts +58 -31
- package/src/server/serverFns/__tests__/ai-hosted-models.test.ts +47 -15
- package/src/server/serverFns/__tests__/ai-source-mode.test.ts +45 -15
- package/src/server/serverFns/__tests__/billing.test.ts +37 -2
- package/src/server/serverFns/__tests__/collab-snapshot.test.ts +41 -14
- package/src/server/serverFns/__tests__/collab.test.ts +41 -14
- package/src/server/serverFns/__tests__/compliance.test.ts +33 -1
- package/src/server/serverFns/__tests__/email-verification.test.ts +35 -1
- package/src/server/serverFns/__tests__/knowledge-breadcrumb.test.ts +46 -6
- package/src/server/serverFns/__tests__/knowledge-delete.test.ts +42 -14
- package/src/server/serverFns/__tests__/knowledge-kb-projects.test.ts +46 -6
- package/src/server/serverFns/__tests__/knowledge-public-compliance.test.ts +36 -2
- package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +42 -10
- package/src/server/serverFns/__tests__/knowledge-search.test.ts +45 -9
- package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +36 -2
- package/src/server/serverFns/__tests__/link-preview.test.ts +38 -4
- package/src/server/serverFns/__tests__/notifications-list.test.ts +36 -3
- package/src/server/serverFns/__tests__/ownership-merge.test.ts +38 -4
- package/src/server/serverFns/__tests__/project-delete-transaction.test.ts +39 -2
- package/src/server/serverFns/__tests__/project-kb.test.ts +34 -1
- package/src/server/serverFns/__tests__/project-member-idor.test.ts +34 -1
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +34 -1
- package/src/server/serverFns/__tests__/read-write-merge.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-comments.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +37 -3
- package/src/server/serverFns/__tests__/story-crud.test.ts +36 -3
- package/src/server/serverFns/__tests__/story-links.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-reactions.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-tree.test.ts +38 -4
- package/src/server/serverFns/__tests__/team-accept.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-invitation.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-join-request.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-members.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-ownership-merge.test.ts +34 -1
- package/src/server/serverFns/__tests__/update-bug-guard.test.ts +37 -3
- package/src/server/serverFns/__tests__/update-document-guard.test.ts +38 -4
- package/src/server/serverFns/__tests__/update-page-guard.test.ts +40 -4
- package/src/server/serverFns/__tests__/update-seats.test.ts +33 -1
- package/src/server/serverFns/__tests__/update-story-guard.test.ts +36 -3
- package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +37 -3
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +36 -2
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +36 -2
- package/src/server/serverFns/__tests__/workspace.test.ts +34 -1
- package/src/server/serverFns/activity.ts +10 -14
- package/src/server/serverFns/agents/agent-crud.ts +16 -15
- package/src/server/serverFns/agents/bindings.ts +12 -13
- package/src/server/serverFns/agents/skill-crud.ts +16 -15
- package/src/server/serverFns/ai/code-mode-readiness.ts +3 -4
- package/src/server/serverFns/ai/search-capability.ts +3 -4
- package/src/server/serverFns/anyhub/app.ts +10 -14
- package/src/server/serverFns/anyhub/index.ts +7 -10
- package/src/server/serverFns/anyhub/install.ts +4 -4
- package/src/server/serverFns/anyhub/permissions.ts +10 -14
- package/src/server/serverFns/anyhub/proxy.ts +4 -6
- package/src/server/serverFns/auth/email-verification.ts +4 -5
- package/src/server/serverFns/auth/session.ts +9 -0
- package/src/server/serverFns/billing.ts +18 -28
- package/src/server/serverFns/blob-upload.ts +6 -9
- package/src/server/serverFns/bug/__tests__/bug-access-tokens.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-activities.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-fix.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +39 -5
- package/src/server/serverFns/bug/__tests__/bug-repro.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-story.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-webhooks.test.ts +34 -1
- package/src/server/serverFns/bug/bug-access-tokens.ts +16 -22
- package/src/server/serverFns/bug/bug-activities.ts +4 -6
- package/src/server/serverFns/bug/bug-collect-tokens.ts +13 -18
- package/src/server/serverFns/bug/bug-comments.ts +13 -18
- package/src/server/serverFns/bug/bug-crud/detail.ts +10 -14
- package/src/server/serverFns/bug/bug-crud.ts +28 -38
- package/src/server/serverFns/bug/bug-display-options.ts +8 -11
- package/src/server/serverFns/bug/bug-export.ts +5 -7
- package/src/server/serverFns/bug/bug-fix.ts +10 -14
- package/src/server/serverFns/bug/bug-groups.ts +10 -14
- package/src/server/serverFns/bug/bug-reactions.ts +10 -14
- package/src/server/serverFns/bug/bug-reorder.ts +4 -6
- package/src/server/serverFns/bug/bug-repro.ts +8 -11
- package/src/server/serverFns/bug/bug-story-bulk.ts +13 -18
- package/src/server/serverFns/bug/bug-story.ts +10 -14
- package/src/server/serverFns/bug/bug-views.ts +12 -18
- package/src/server/serverFns/bug/bug-webhooks.ts +13 -18
- package/src/server/serverFns/bug/test-bug-confirm.ts +7 -10
- package/src/server/serverFns/changelog/__tests__/fetch.test.ts +40 -1
- package/src/server/serverFns/changelog/fetch.ts +4 -6
- package/src/server/serverFns/chat/conversations.ts +17 -28
- package/src/server/serverFns/chat/list-messages.ts +4 -7
- package/src/server/serverFns/collab-presence.ts +11 -13
- package/src/server/serverFns/collab-snapshot.ts +6 -7
- package/src/server/serverFns/collab.ts +7 -7
- package/src/server/serverFns/compliance.ts +8 -8
- package/src/server/serverFns/design/__tests__/comments.test.ts +54 -10
- package/src/server/serverFns/design/__tests__/designs.test.ts +39 -16
- package/src/server/serverFns/design/__tests__/page-versions.test.ts +52 -14
- package/src/server/serverFns/design/__tests__/shares-password.test.ts +44 -14
- package/src/server/serverFns/design/__tests__/shares.test.ts +44 -14
- package/src/server/serverFns/design/__tests__/versions.test.ts +44 -14
- package/src/server/serverFns/design/ai/__tests__/agent-conversations.test.ts +50 -21
- package/src/server/serverFns/design/ai/__tests__/providers.test.ts +42 -8
- package/src/server/serverFns/design/ai/agent-conversations.ts +19 -26
- package/src/server/serverFns/design/ai/fill-annotation.ts +5 -7
- package/src/server/serverFns/design/ai/generate-prototype.ts +5 -7
- package/src/server/serverFns/design/ai/hosted-models.ts +4 -5
- package/src/server/serverFns/design/ai/providers.ts +30 -39
- package/src/server/serverFns/design/ai/source-mode.ts +10 -12
- package/src/server/serverFns/design/comments.ts +14 -13
- package/src/server/serverFns/design/designs.ts +13 -19
- package/src/server/serverFns/design/page-versions.ts +17 -18
- package/src/server/serverFns/design/pages.ts +17 -18
- package/src/server/serverFns/design/reviews.ts +8 -9
- package/src/server/serverFns/design/shares-password.ts +13 -19
- package/src/server/serverFns/design/shares.ts +22 -31
- package/src/server/serverFns/design/versions.ts +8 -9
- package/src/server/serverFns/harness/__tests__/attach-prototype.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-kb.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-story-ssrf.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-story.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/cancel-run.test.ts +40 -15
- package/src/server/serverFns/harness/__tests__/extract-attachment.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/gallery.spec.ts +76 -12
- package/src/server/serverFns/harness/__tests__/github-workspace.spec.ts +38 -6
- package/src/server/serverFns/harness/__tests__/media-prototype.spec.ts +50 -6
- package/src/server/serverFns/harness/__tests__/media.spec.ts +55 -11
- package/src/server/serverFns/harness/__tests__/sandbox-policy.test.ts +41 -16
- package/src/server/serverFns/harness/__tests__/workspace-files.test.ts +41 -16
- package/src/server/serverFns/harness/attach-prototype.ts +4 -4
- package/src/server/serverFns/harness/attach-to-kb.ts +4 -4
- package/src/server/serverFns/harness/attach-to-story.ts +4 -4
- package/src/server/serverFns/harness/cancel-run.ts +4 -6
- package/src/server/serverFns/harness/extract-attachment.ts +3 -4
- package/src/server/serverFns/harness/gallery.ts +19 -23
- package/src/server/serverFns/harness/github-workspace.ts +14 -21
- package/src/server/serverFns/harness/media.ts +18 -117
- package/src/server/serverFns/harness/sandbox-policy.ts +11 -10
- package/src/server/serverFns/harness/search-mention-candidates.ts +4 -6
- package/src/server/serverFns/harness/workspace-files.ts +19 -26
- package/src/server/serverFns/iteration/__tests__/iteration-auto-schedule.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +35 -3
- package/src/server/serverFns/iteration/iteration-auto-schedule.ts +7 -10
- package/src/server/serverFns/iteration/iteration-burndown.ts +4 -6
- package/src/server/serverFns/iteration/iteration-crud.ts +26 -35
- package/src/server/serverFns/iteration/iteration-stories.ts +10 -14
- package/src/server/serverFns/iteration/iteration-transfer.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/categories.ts +11 -15
- package/src/server/serverFns/knowledge/public/bookmarks/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/bookmarks/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/favorite.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/refresh-favicon.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/sync-github.ts +5 -7
- package/src/server/serverFns/knowledge/public/bookmarks/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/update.ts +6 -8
- package/src/server/serverFns/knowledge/public/compliance/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/compliance/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/update.ts +5 -7
- package/src/server/serverFns/knowledge/public/dictionary/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/dictionary/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/update.ts +5 -7
- package/src/server/serverFns/knowledge/public/entity-tags.ts +4 -6
- package/src/server/serverFns/knowledge/public/favorite.ts +7 -10
- package/src/server/serverFns/knowledge/public/legal/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/legal/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/update.ts +5 -7
- package/src/server/serverFns/knowledge-index.ts +12 -16
- package/src/server/serverFns/knowledge.ts +50 -67
- package/src/server/serverFns/notifications.ts +25 -34
- package/src/server/serverFns/plugins/harness-plugins.ts +16 -23
- package/src/server/serverFns/project/module-overview.ts +7 -10
- package/src/server/serverFns/project.ts +48 -68
- package/src/server/serverFns/references/create.ts +4 -6
- package/src/server/serverFns/references/resolve.ts +4 -6
- package/src/server/serverFns/references/search.ts +4 -6
- package/src/server/serverFns/share/design/[designId]/events.ts +4 -6
- package/src/server/serverFns/sso.ts +1 -1
- package/src/server/serverFns/story/__tests__/bulk.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-attachments.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +42 -17
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-read-path-authz.test.ts +40 -4
- package/src/server/serverFns/story/__tests__/story-search.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-transfer.test.ts +34 -1
- package/src/server/serverFns/story/link-preview.ts +5 -7
- package/src/server/serverFns/story/story-assoc.ts +28 -38
- package/src/server/serverFns/story/story-attachments.ts +7 -10
- package/src/server/serverFns/story/story-children.ts +4 -6
- package/src/server/serverFns/story/story-comments.ts +13 -18
- package/src/server/serverFns/story/story-crud/activities.ts +4 -6
- package/src/server/serverFns/story/story-crud/archive.ts +7 -10
- package/src/server/serverFns/story/story-crud/bulk.ts +10 -14
- package/src/server/serverFns/story/story-crud/detail.ts +7 -10
- package/src/server/serverFns/story/story-crud/label-suggest.ts +4 -6
- package/src/server/serverFns/story/story-crud/labels.ts +6 -9
- package/src/server/serverFns/story/story-crud/user-brief.ts +4 -6
- package/src/server/serverFns/story/story-crud.ts +13 -18
- package/src/server/serverFns/story/story-dependencies.ts +16 -22
- package/src/server/serverFns/story/story-groups.ts +10 -14
- package/src/server/serverFns/story/story-links.ts +13 -18
- package/src/server/serverFns/story/story-reactions.ts +13 -18
- package/src/server/serverFns/story/story-search.ts +7 -10
- package/src/server/serverFns/story/story-transfer.ts +10 -14
- package/src/server/serverFns/story/story-tree.ts +7 -10
- package/src/server/serverFns/story-display-options.ts +7 -8
- package/src/server/serverFns/story-view.ts +12 -18
- package/src/server/serverFns/tasks/task-crud.ts +13 -17
- package/src/server/serverFns/tasks/task-queries.ts +12 -18
- package/src/server/serverFns/team.ts +46 -66
- package/src/server/serverFns/testcase/__tests__/automation.test.ts +38 -5
- package/src/server/serverFns/testcase/__tests__/evidence-upload.test.ts +37 -3
- package/src/server/serverFns/testcase/__tests__/generation.test.ts +38 -5
- package/src/server/serverFns/testcase/__tests__/plans.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/regression.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/run-cases.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/runs.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/stats.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +39 -5
- package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-folders.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +38 -5
- package/src/server/serverFns/testcase/automation.ts +13 -17
- package/src/server/serverFns/testcase/bulk.ts +10 -14
- package/src/server/serverFns/testcase/evidence-upload.ts +5 -7
- package/src/server/serverFns/testcase/generation.ts +10 -13
- package/src/server/serverFns/testcase/plans.ts +23 -31
- package/src/server/serverFns/testcase/regression.ts +7 -10
- package/src/server/serverFns/testcase/run-cases.ts +13 -18
- package/src/server/serverFns/testcase/runs.ts +13 -18
- package/src/server/serverFns/testcase/stats.ts +13 -18
- package/src/server/serverFns/testcase/testcase-activities.ts +4 -6
- package/src/server/serverFns/testcase/testcase-board.ts +4 -6
- package/src/server/serverFns/testcase/testcase-crud.ts +20 -27
- package/src/server/serverFns/testcase/testcase-display-options.ts +8 -11
- package/src/server/serverFns/testcase/testcase-export.ts +5 -7
- package/src/server/serverFns/testcase/testcase-filter-views.ts +12 -18
- package/src/server/serverFns/testcase/testcase-folders.ts +16 -22
- package/src/server/serverFns/testcase/testcase-groups.ts +4 -6
- package/src/server/serverFns/testcase/testcase-reorder.ts +4 -6
- package/src/server/serverFns/translation.ts +4 -6
- package/src/server/serverFns/user/avatar.ts +4 -6
- package/src/server/serverFns/user/settings.ts +4 -6
- package/src/server/serverFns/user/update-locale.ts +4 -6
- package/src/server/serverFns/workspace-kb.ts +26 -35
- package/src/server/serverFns/workspace.ts +43 -59
- package/src/server/serverFns/ws.ts +4 -6
- package/src/server/serverfn-public-allowlist.ts +117 -0
- package/src/server/story/types.ts +0 -1
- package/src/server/sync/__tests__/scopes.test.ts +64 -0
- package/src/server/sync/scopes.ts +7 -0
- package/src/server/testcase/__tests__/auto-bug.test.ts +44 -12
- package/src/__tests__/bench/baselines/format-timestamp.json +0 -44
- package/src/__typeprobe__.d.ts +0 -4
- package/src/components/forms/app-form-dialog.tsx +0 -126
- package/src/components/forms/hooks/use-form-submit.ts +0 -57
- package/src/components/forms/types.ts +0 -34
- package/src/components/harness/code/workspace-picker.tsx +0 -239
- package/src/components/tiptap/extensions/local-file/insert-local-file.ts +0 -35
- package/src/components/tiptap/extensions/local-file/local-file-node.ts +0 -71
- package/src/devframe-docks/ai-chat-iframe.tsx +0 -29
- package/src/devframe-docks/session-iframe.tsx +0 -90
- package/src/lib/__tests__/desktop-detect.test.ts +0 -19
- package/src/lib/collections/index.ts +0 -22
- package/src/lib/collections/knowledge/index.ts +0 -18
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +0 -34
- package/src/lib/collections/reference/bookmark-categories-collection.ts +0 -31
- package/src/lib/collections/reference/labels-collection.ts +0 -31
- package/src/lib/collections/reference/projects-collection.ts +0 -31
- package/src/lib/collections/reference/users-brief-collection.ts +0 -35
- package/src/lib/collections/reference/workspaces-collection.ts +0 -34
- package/src/lib/desktop-detect.ts +0 -29
- package/src/paraglide/messages/en.js +0 -28260
- package/src/paraglide/messages/zh.js +0 -28258
|
@@ -75,7 +75,22 @@ vi.mock("@/server/serverFns/design/pages", () => ({
|
|
|
75
75
|
vi.mock("@/server/serverFns/design/designs", () => ({
|
|
76
76
|
updateDesignFn: vi.fn(),
|
|
77
77
|
}));
|
|
78
|
-
vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {} }));
|
|
78
|
+
vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {}, useAppHotkeys: () => appHotkeys }));
|
|
79
|
+
|
|
80
|
+
// DesignEditorLayout 挂载 use-unsaved-guard(任务 2):无 RouterProvider 的
|
|
81
|
+
// 单测环境把 useBlocker 替换为 idle resolver(真实 blocker 注册属 router
|
|
82
|
+
// 集成行为,见 hooks/__tests__/use-unsaved-guard.test.tsx)
|
|
83
|
+
vi.mock("@tanstack/react-router", async (importOriginal) => ({
|
|
84
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
85
|
+
useBlocker: () => ({
|
|
86
|
+
status: "idle" as const,
|
|
87
|
+
current: undefined,
|
|
88
|
+
next: undefined,
|
|
89
|
+
action: undefined,
|
|
90
|
+
proceed: undefined,
|
|
91
|
+
reset: undefined,
|
|
92
|
+
}),
|
|
93
|
+
}));
|
|
79
94
|
vi.mock("@/components/design/editor/versions/page-version-panel", () => ({
|
|
80
95
|
PageVersionPanel: () => null,
|
|
81
96
|
}));
|
|
@@ -88,6 +103,7 @@ vi.mock("@/components/design/editor/layout/design-doc-panel", () => ({
|
|
|
88
103
|
|
|
89
104
|
import { DesignRightSidebar } from "../design-right-sidebar";
|
|
90
105
|
import { DesignEditorLayout } from "../design-editor-layout";
|
|
106
|
+
import { appHotkeys } from "@/lib/hotkeys/registry";
|
|
91
107
|
import type { Design } from "@allbluecn/kernel";
|
|
92
108
|
|
|
93
109
|
const designFixture = {
|
|
@@ -72,10 +72,26 @@ vi.mock("@/server/serverFns/design/pages", () => ({
|
|
|
72
72
|
vi.mock("@/server/serverFns/design/designs", () => ({
|
|
73
73
|
updateDesignFn: vi.fn(),
|
|
74
74
|
}));
|
|
75
|
-
vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {} }));
|
|
75
|
+
vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {}, useAppHotkeys: () => appHotkeys }));
|
|
76
|
+
|
|
77
|
+
// DesignEditorLayout 挂载 use-unsaved-guard(任务 2):本冒烟测试无
|
|
78
|
+
// RouterProvider(node + renderToString),把 useBlocker 替换为 idle
|
|
79
|
+
// resolver——真实 blocker 注册属 router 集成行为,SSR 渲染期本就零 window 访问
|
|
80
|
+
vi.mock("@tanstack/react-router", async (importOriginal) => ({
|
|
81
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
82
|
+
useBlocker: () => ({
|
|
83
|
+
status: "idle" as const,
|
|
84
|
+
current: undefined,
|
|
85
|
+
next: undefined,
|
|
86
|
+
action: undefined,
|
|
87
|
+
proceed: undefined,
|
|
88
|
+
reset: undefined,
|
|
89
|
+
}),
|
|
90
|
+
}));
|
|
76
91
|
|
|
77
92
|
import { DesignRightSidebar } from "../design-right-sidebar";
|
|
78
93
|
import { DesignEditorLayout } from "../design-editor-layout";
|
|
94
|
+
import { appHotkeys } from "@/lib/hotkeys/registry";
|
|
79
95
|
import { useTrackColumns } from "@/components/harness/workbench/use-track-columns";
|
|
80
96
|
|
|
81
97
|
const designFixture = {
|
|
@@ -21,7 +21,7 @@ import { emitEvent, AppEvents } from "@/lib/events";
|
|
|
21
21
|
import { DesignLeftSidebar } from "./design-left-sidebar";
|
|
22
22
|
import { DesignRightSidebar } from "./design-right-sidebar";
|
|
23
23
|
import { DesignNavigation } from "./design-navigation";
|
|
24
|
-
import { useHotkey } from "@tanstack/react-hotkeys";
|
|
24
|
+
import { useAppHotkeys, useHotkey } from "@tanstack/react-hotkeys";
|
|
25
25
|
import type { Editor } from "@tiptap/react";
|
|
26
26
|
import { AnnotationOverlay } from "@/components/design/editor/annotation/overlay/annotation-overlay";
|
|
27
27
|
import { ElementContext } from "./element-context";
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
import type { PrototypeScene } from "@/server/serverFns/design/ai/generate-prototype";
|
|
65
65
|
import { useDebouncer } from "@/lib/pacer";
|
|
66
66
|
import { isCollabEnabled } from "@/lib/collab/env";
|
|
67
|
+
import { useUnsavedGuard } from "@/hooks/use-unsaved-guard";
|
|
67
68
|
import { useDesignLeftPanelPreference } from "./hooks/use-design-left-panel-preference";
|
|
68
69
|
import { useDesignPageLoad } from "./hooks/use-design-page-load";
|
|
69
70
|
import { usePageVersionEvents } from "./hooks/use-page-version-events";
|
|
@@ -569,6 +570,13 @@ export function DesignEditorLayout({ prd: initialDesign }: DesignEditorLayoutPro
|
|
|
569
570
|
collab: collabEnabled,
|
|
570
571
|
});
|
|
571
572
|
|
|
573
|
+
// 未保存拦截(任务 2):SPA 导航 dirty 弹 AlertDialog,刷新/关闭走
|
|
574
|
+
// enableBeforeUnload 原生警告。协同态豁免:collab 下双通道保存让位(T29)
|
|
575
|
+
// dirty 恒真不代表未落库(Y.Doc 是权威副本),拦截反成误报
|
|
576
|
+
const unsavedGuard = useUnsavedGuard({
|
|
577
|
+
isDirty: () => !collabEnabled && (loaded.canvasDirty || loaded.docDirty),
|
|
578
|
+
});
|
|
579
|
+
|
|
572
580
|
const switchPage = useCallback(async (pageId: string) => {
|
|
573
581
|
if (loaded.canvasDirty && loaded.activePageId) await autoSave.saveCanvasRef.current();
|
|
574
582
|
if (loaded.docDirty && loaded.activePageId) await autoSave.saveDoc();
|
|
@@ -588,7 +596,9 @@ export function DesignEditorLayout({ prd: initialDesign }: DesignEditorLayoutPro
|
|
|
588
596
|
}
|
|
589
597
|
}, [prd.id]);
|
|
590
598
|
|
|
591
|
-
|
|
599
|
+
// 声明即绑定:save 条目单一来源(lib/hotkeys/registry.ts),禁止手写 chord
|
|
600
|
+
const hk = useAppHotkeys();
|
|
601
|
+
useHotkey(hk["save"], autoSave.saveAll);
|
|
592
602
|
|
|
593
603
|
const [aiModel, setAiModel] = useState("");
|
|
594
604
|
|
|
@@ -669,6 +679,7 @@ export function DesignEditorLayout({ prd: initialDesign }: DesignEditorLayoutPro
|
|
|
669
679
|
</CanvasProvider>
|
|
670
680
|
</div>
|
|
671
681
|
</div>
|
|
682
|
+
{unsavedGuard.dialog}
|
|
672
683
|
</div>
|
|
673
684
|
</ElementContext.Provider>
|
|
674
685
|
);
|
|
@@ -182,30 +182,14 @@ export function useDesignAutoSave({
|
|
|
182
182
|
await Promise.all([saveCanvasRef.current("manual"), saveDoc("manual")]);
|
|
183
183
|
}, [canvasDebouncer, saveDoc]);
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
canvasDirtyRef.current = canvasDirty;
|
|
185
|
+
// 未保存拦截(任务 2):手写 beforeunload preventDefault 已迁
|
|
186
|
+
// use-unsaved-guard(useBlocker + enableBeforeUnload 函数形态,挂载于
|
|
187
|
+
// design-editor-layout),SPA 导航与刷新统一由该 hook 拦截
|
|
189
188
|
|
|
190
189
|
useEffect(() => {
|
|
191
190
|
if (activePageId) noPageToastShownRef.current = false;
|
|
192
191
|
}, [activePageId]);
|
|
193
192
|
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
196
|
-
canvasDebouncer.cancel();
|
|
197
|
-
docDebouncer.cancel();
|
|
198
|
-
// 关闭/刷新前若有未落库编辑(防抖窗口内的内容已被 cancel 丢弃),
|
|
199
|
-
// 触发浏览器原生「未保存更改」确认,给用户阻止误关的机会
|
|
200
|
-
if (canvasDirtyRef.current || docDirtyRef.current) {
|
|
201
|
-
e.preventDefault();
|
|
202
|
-
e.returnValue = "";
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
206
|
-
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
207
|
-
}, [canvasDebouncer, docDebouncer]);
|
|
208
|
-
|
|
209
193
|
const handleDocContentChange = useCallback((content: DocContent) => {
|
|
210
194
|
setDocContent(content);
|
|
211
195
|
setDocDirty(true);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { Suspense, lazy, useEffect, useState } from "react";
|
|
19
|
+
|
|
20
|
+
// DEV 门控放在模块顶层三元上:生产构建时 import.meta.env.DEV 被静态替换为
|
|
21
|
+
// false,动态 import 落入死分支被 tree-shake,devtools chunk 不进生产产物。
|
|
22
|
+
const ReactQueryDevtools = import.meta.env.DEV
|
|
23
|
+
? lazy(() =>
|
|
24
|
+
import("@tanstack/react-query-devtools").then((m) => ({
|
|
25
|
+
default: m.ReactQueryDevtools,
|
|
26
|
+
})),
|
|
27
|
+
)
|
|
28
|
+
: null;
|
|
29
|
+
|
|
30
|
+
const TanStackRouterDevtools = import.meta.env.DEV
|
|
31
|
+
? lazy(() =>
|
|
32
|
+
import("@tanstack/react-router-devtools").then((m) => ({
|
|
33
|
+
default: m.TanStackRouterDevtools,
|
|
34
|
+
})),
|
|
35
|
+
)
|
|
36
|
+
: null;
|
|
37
|
+
|
|
38
|
+
export function DevtoolsPanel() {
|
|
39
|
+
// SSR 安全:dev 模式下 SSR 渲染期(mounted=false)返回 null,挂载后再
|
|
40
|
+
// 加载面板——lazy 组件只在客户端渲染,hydration 前后零差异。
|
|
41
|
+
const [mounted, setMounted] = useState(false);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setMounted(true);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
if (!import.meta.env.DEV || !mounted) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Suspense fallback={null}>
|
|
50
|
+
{ReactQueryDevtools ? <ReactQueryDevtools initialIsOpen={false} /> : null}
|
|
51
|
+
{TanStackRouterDevtools ? (
|
|
52
|
+
<TanStackRouterDevtools initialIsOpen={false} />
|
|
53
|
+
) : null}
|
|
54
|
+
</Suspense>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { describe, it, expect, vi } from "vitest";
|
|
19
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { useAppForm } from "../create-app-form";
|
|
22
|
+
|
|
23
|
+
const schema = z.object({
|
|
24
|
+
name: z.string().min(2, "名称至少2个字"),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
type SubmitValues = { name: string };
|
|
28
|
+
|
|
29
|
+
type SubmitFn = (value: SubmitValues) => Promise<void> | void;
|
|
30
|
+
|
|
31
|
+
function Harness({ onSubmit, initialValue = "" }: {
|
|
32
|
+
onSubmit: SubmitFn;
|
|
33
|
+
initialValue?: string;
|
|
34
|
+
}) {
|
|
35
|
+
const form = useAppForm({
|
|
36
|
+
defaultValues: { name: initialValue },
|
|
37
|
+
validators: [{ run: schema, triggers: ["change"] }],
|
|
38
|
+
onSubmit: ({ value }) => onSubmit(value),
|
|
39
|
+
});
|
|
40
|
+
return (
|
|
41
|
+
<form.AppForm>
|
|
42
|
+
<form
|
|
43
|
+
onSubmit={(e) => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
form.handleSubmit();
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<form.AppField name="name" label="名称" />
|
|
50
|
+
<button type="submit">提交</button>
|
|
51
|
+
</form>
|
|
52
|
+
</form.AppForm>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function SubmitHarness({ onSubmit }: { onSubmit: SubmitFn }) {
|
|
57
|
+
const form = useAppForm({
|
|
58
|
+
defaultValues: { name: "有效名称" },
|
|
59
|
+
validators: [{ run: schema, triggers: ["change"] }],
|
|
60
|
+
onSubmit: ({ value }) => onSubmit(value),
|
|
61
|
+
});
|
|
62
|
+
return (
|
|
63
|
+
<form.AppForm>
|
|
64
|
+
<form
|
|
65
|
+
onSubmit={(e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
e.stopPropagation();
|
|
68
|
+
form.handleSubmit();
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<form.AppField name="name" />
|
|
72
|
+
<form.AppSubmit>提交</form.AppSubmit>
|
|
73
|
+
</form>
|
|
74
|
+
</form.AppForm>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe("create-app-form 工厂", () => {
|
|
79
|
+
it("useAppForm 渲染 form.AppField 默认输入与 AppSubmit 按钮", () => {
|
|
80
|
+
render(<SubmitHarness onSubmit={vi.fn()} />);
|
|
81
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByRole("button", { name: "提交" })).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("校验错误经 AppFieldError 在 touch 后显示(isTouched 守卫)", async () => {
|
|
86
|
+
render(<Harness onSubmit={vi.fn()} />);
|
|
87
|
+
const input = screen.getByRole("textbox");
|
|
88
|
+
fireEvent.change(input, { target: { value: "A" } });
|
|
89
|
+
// 未 touch:isTouched 守卫生效,错误不显示
|
|
90
|
+
expect(screen.queryByText("名称至少2个字")).not.toBeInTheDocument();
|
|
91
|
+
fireEvent.blur(input);
|
|
92
|
+
await waitFor(() =>
|
|
93
|
+
expect(screen.getByText("名称至少2个字")).toBeInTheDocument(),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("提交被校验拦截时字段已 touch、错误可见;有效值提交透传 values", async () => {
|
|
98
|
+
const onSubmit = vi.fn();
|
|
99
|
+
render(<Harness onSubmit={onSubmit} initialValue="A" />);
|
|
100
|
+
const input = screen.getByRole("textbox");
|
|
101
|
+
// 无效值直接提交:form-core 提交流程先 touch 全部字段再拦截 onSubmit
|
|
102
|
+
fireEvent.click(screen.getByRole("button", { name: "提交" }));
|
|
103
|
+
await waitFor(() =>
|
|
104
|
+
expect(screen.getByText("名称至少2个字")).toBeInTheDocument(),
|
|
105
|
+
);
|
|
106
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
107
|
+
|
|
108
|
+
fireEvent.change(input, { target: { value: "有效名称" } });
|
|
109
|
+
fireEvent.click(screen.getByRole("button", { name: "提交" }));
|
|
110
|
+
await waitFor(() =>
|
|
111
|
+
expect(onSubmit).toHaveBeenCalledWith({ name: "有效名称" }),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("form.AppSubmit 走 form.Subscribe isSubmitting 单通道:提交中禁用", async () => {
|
|
116
|
+
let resolveSubmit: () => void = () => {};
|
|
117
|
+
const deferred = new Promise<void>((resolve) => {
|
|
118
|
+
resolveSubmit = resolve;
|
|
119
|
+
});
|
|
120
|
+
const onSubmit = vi.fn(() => deferred);
|
|
121
|
+
|
|
122
|
+
render(<SubmitHarness onSubmit={onSubmit} />);
|
|
123
|
+
const button = screen.getByRole("button", { name: "提交" });
|
|
124
|
+
expect(button).not.toBeDisabled();
|
|
125
|
+
fireEvent.click(button);
|
|
126
|
+
await waitFor(() => expect(button).toBeDisabled());
|
|
127
|
+
resolveSubmit();
|
|
128
|
+
await waitFor(() => expect(button).not.toBeDisabled());
|
|
129
|
+
expect(onSubmit).toHaveBeenCalledWith({ name: "有效名称" });
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -17,8 +17,28 @@
|
|
|
17
17
|
|
|
18
18
|
import type { AnyFieldApi } from "@tanstack/react-form"
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 字段错误可见性的唯一口径:touch 守卫(blur 或提交尝试后)+ 校验未过。
|
|
22
|
+
* 表单库在每次提交尝试会 touch 全部字段,故提交被校验拦截时错误必然可见。
|
|
23
|
+
*/
|
|
24
|
+
export function hasVisibleFieldError(field: AnyFieldApi): boolean {
|
|
25
|
+
return field.meta.isTouched && !field.meta.isValid
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 首条可见错误文本,供自绘错误 UI(IconInput/PasswordInput 等)消费。
|
|
30
|
+
* 与 AppFieldError 共享同一守卫语义。
|
|
31
|
+
*/
|
|
32
|
+
export function firstFieldErrorMessage(field: AnyFieldApi): string | undefined {
|
|
33
|
+
if (!hasVisibleFieldError(field)) return undefined
|
|
34
|
+
const first = field.errors
|
|
35
|
+
.map((e) => (typeof e === "string" ? e : e?.message ?? String(e)))
|
|
36
|
+
.find(Boolean)
|
|
37
|
+
return first ?? undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
export function AppFieldError({ field }: { field: AnyFieldApi }) {
|
|
21
|
-
if (!field
|
|
41
|
+
if (!hasVisibleFieldError(field)) return null
|
|
22
42
|
return (
|
|
23
43
|
<span className="block text-sm text-destructive mt-1">
|
|
24
44
|
{/* 校验错误可为纯字符串(无 message 属性),直接渲染会输出字面量 undefined */}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type {
|
|
19
19
|
AnyFieldApi,
|
|
20
20
|
AnyReactFormApi,
|
|
21
|
+
FieldValidators,
|
|
21
22
|
} from "@tanstack/react-form"
|
|
22
23
|
import type { ReactNode } from "react"
|
|
23
24
|
import {
|
|
@@ -29,19 +30,18 @@ import {
|
|
|
29
30
|
import { Input } from "@/components/ui/input"
|
|
30
31
|
import { AppFieldError } from "./app-field-error"
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
* TODO(待决策):TanStack Form v2(alpha)的 validators 泛型尚未稳定,
|
|
34
|
-
* 精确映射 FieldValidators 需随 v2 正式版补全;当前经 AnyReactFormApi 通道透传。
|
|
35
|
-
*/
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
-
export type AppFieldValidators<TValue> = any
|
|
38
|
-
|
|
39
33
|
export interface AppFieldProps<TValue> {
|
|
40
34
|
name: string
|
|
41
35
|
form: AnyReactFormApi
|
|
42
36
|
label?: string
|
|
43
37
|
description?: string
|
|
44
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 字段级校验器(TanStack Form v2 Validator 配置数组)。值类型经 TValue 约束;
|
|
40
|
+
* 所属表单在 App* 封装层为 any 擦除(AnyReactFormApi 通道),由 useAppForm
|
|
41
|
+
* 的表单级 validators 承担精确类型。
|
|
42
|
+
*/
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 表单数据形状在封装层擦除,与 AnyFieldApi 同口径
|
|
44
|
+
validators?: FieldValidators<any, string, TValue>
|
|
45
45
|
children?: (field: AnyFieldApi) => ReactNode
|
|
46
46
|
renderError?: boolean
|
|
47
47
|
}
|
|
@@ -20,22 +20,25 @@ import type { ReactNode } from "react"
|
|
|
20
20
|
import type { AnyReactFormApi } from "@tanstack/react-form"
|
|
21
21
|
import { Button } from "@/components/ui/button"
|
|
22
22
|
|
|
23
|
+
export interface AppSubmitProps {
|
|
24
|
+
/** 显式传 form 走 Subscribe isSubmitting 单通道;免传时用工厂产物 form.AppSubmit */
|
|
25
|
+
form?: AnyReactFormApi
|
|
26
|
+
isSubmitting?: boolean
|
|
27
|
+
children: ReactNode
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
export function AppSubmit({
|
|
24
32
|
form,
|
|
25
33
|
isSubmitting,
|
|
26
34
|
children,
|
|
27
35
|
disabled = false,
|
|
28
|
-
}: {
|
|
29
|
-
form?: AnyReactFormApi
|
|
30
|
-
isSubmitting?: boolean
|
|
31
|
-
children: ReactNode
|
|
32
|
-
disabled?: boolean
|
|
33
|
-
}) {
|
|
36
|
+
}: AppSubmitProps) {
|
|
34
37
|
if (form && isSubmitting === undefined) {
|
|
35
38
|
return (
|
|
36
|
-
<form.Subscribe
|
|
37
|
-
selector={(state
|
|
38
|
-
|
|
39
|
+
<form.Subscribe
|
|
40
|
+
selector={(state) => state.isSubmitting}
|
|
41
|
+
children={(isSub) => (
|
|
39
42
|
<Button type="submit" disabled={isSub || disabled}>
|
|
40
43
|
{isSub ? (
|
|
41
44
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createFormHook, getFormHookHelpers } from "@tanstack/react-form"
|
|
19
|
+
import { AppCheckbox, type AppCheckboxProps } from "./app-checkbox"
|
|
20
|
+
import { AppCombobox, type AppComboboxProps } from "./app-combobox"
|
|
21
|
+
import { AppDatePicker, type AppDatePickerProps } from "./app-date-picker"
|
|
22
|
+
import { AppField, type AppFieldProps } from "./app-field"
|
|
23
|
+
import { AppFieldError } from "./app-field-error"
|
|
24
|
+
import { AppInput, type AppInputProps } from "./app-input"
|
|
25
|
+
import { AppArrayField, type AppArrayFieldProps } from "./app-array-field"
|
|
26
|
+
import { AppSection } from "./app-section"
|
|
27
|
+
import { AppSelect, type AppSelectProps } from "./app-select"
|
|
28
|
+
import { AppSubmit, type AppSubmitProps } from "./app-submit"
|
|
29
|
+
import { AppSwitch, type AppSwitchProps } from "./app-switch"
|
|
30
|
+
import { AppTextarea, type AppTextareaProps } from "./app-textarea"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* App* 表单套件的唯一消费入口(TanStack Form v2 createFormHook 工厂)。
|
|
34
|
+
*
|
|
35
|
+
* - `useAppForm`:创建携带已注册组件树的表单 API;表单树内经
|
|
36
|
+
* `form.AppField` / `field.AppFieldError` 等按名消费,无需再显式传 `form`。
|
|
37
|
+
* - `formComponents`:挂在表单 API 上的组件(`form.AppXxx`)。它们经由
|
|
38
|
+
* useFormContext 从最近的 `form.AppForm` 取表单——因此使用这些组件的
|
|
39
|
+
* 子树必须包在 `<form.AppForm>` 内。
|
|
40
|
+
* - `fieldComponents`:挂在字段 API 上的组件(`field.AppXxx`),由
|
|
41
|
+
* fieldComponent 助手从字段上下文注入当前 field,仅在 `form.Field`
|
|
42
|
+
* 渲染回调内可用。
|
|
43
|
+
* - 提交状态单通道:一切提交态消费 `form.Subscribe isSubmitting`
|
|
44
|
+
* (form.AppSubmit 内部同源),禁止另立 useState loading 通道。
|
|
45
|
+
*/
|
|
46
|
+
const { fieldComponent } = getFormHookHelpers()
|
|
47
|
+
|
|
48
|
+
/** AppFieldError 免传 field:由字段上下文注入(isTouched 守卫语义见其实现) */
|
|
49
|
+
const AppFieldErrorBound = fieldComponent.loose(AppFieldError, "field")
|
|
50
|
+
|
|
51
|
+
// useFormContext 仅由 createFormHook 返回(包内不单独导出),绑定层组件在
|
|
52
|
+
// 渲染期才调用它;函数声明提升保证上方 options 求值时绑定组件已可用。
|
|
53
|
+
const { useAppForm, appFormOptions, useFormContext } = createFormHook({
|
|
54
|
+
formComponents: {
|
|
55
|
+
AppField: AppFieldBound,
|
|
56
|
+
AppInput: AppInputBound,
|
|
57
|
+
AppTextarea: AppTextareaBound,
|
|
58
|
+
AppCheckbox: AppCheckboxBound,
|
|
59
|
+
AppSwitch: AppSwitchBound,
|
|
60
|
+
AppSelect: AppSelectBound,
|
|
61
|
+
AppCombobox: AppComboboxBound,
|
|
62
|
+
AppDatePicker: AppDatePickerBound,
|
|
63
|
+
AppArrayField: AppArrayFieldBound,
|
|
64
|
+
AppSubmit: AppSubmitBound,
|
|
65
|
+
AppSection,
|
|
66
|
+
},
|
|
67
|
+
fieldComponents: {
|
|
68
|
+
AppFieldError: AppFieldErrorBound,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export { useAppForm, appFormOptions }
|
|
73
|
+
|
|
74
|
+
function AppFieldBound<TValue>(props: Omit<AppFieldProps<TValue>, "form">) {
|
|
75
|
+
const form = useFormContext()
|
|
76
|
+
return <AppField {...props} form={form} />
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function AppInputBound(props: Omit<AppInputProps, "form">) {
|
|
80
|
+
const form = useFormContext()
|
|
81
|
+
return <AppInput {...props} form={form} />
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function AppTextareaBound(props: Omit<AppTextareaProps, "form">) {
|
|
85
|
+
const form = useFormContext()
|
|
86
|
+
return <AppTextarea {...props} form={form} />
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function AppCheckboxBound(props: Omit<AppCheckboxProps, "form">) {
|
|
90
|
+
const form = useFormContext()
|
|
91
|
+
return <AppCheckbox {...props} form={form} />
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function AppSwitchBound(props: Omit<AppSwitchProps, "form">) {
|
|
95
|
+
const form = useFormContext()
|
|
96
|
+
return <AppSwitch {...props} form={form} />
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function AppSelectBound(props: Omit<AppSelectProps, "form">) {
|
|
100
|
+
const form = useFormContext()
|
|
101
|
+
return <AppSelect {...props} form={form} />
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function AppComboboxBound(props: Omit<AppComboboxProps, "form">) {
|
|
105
|
+
const form = useFormContext()
|
|
106
|
+
return <AppCombobox {...props} form={form} />
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function AppDatePickerBound(props: Omit<AppDatePickerProps, "form">) {
|
|
110
|
+
const form = useFormContext()
|
|
111
|
+
return <AppDatePicker {...props} form={form} />
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function AppArrayFieldBound<TItem>(
|
|
115
|
+
props: Omit<AppArrayFieldProps<TItem>, "form">,
|
|
116
|
+
) {
|
|
117
|
+
const form = useFormContext()
|
|
118
|
+
return <AppArrayField {...props} form={form} />
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function AppSubmitBound(props: Omit<AppSubmitProps, "form">) {
|
|
122
|
+
const form = useFormContext()
|
|
123
|
+
return <AppSubmit {...props} form={form} />
|
|
124
|
+
}
|
|
@@ -26,6 +26,5 @@ export { AppDatePicker } from "./app-date-picker"
|
|
|
26
26
|
export { AppArrayField, type AppArrayFieldProps } from "./app-array-field"
|
|
27
27
|
export { AppSection } from "./app-section"
|
|
28
28
|
export { AppSubmit } from "./app-submit"
|
|
29
|
-
export {
|
|
30
|
-
export {
|
|
31
|
-
export type { FormSubmitConfig, AsyncValidator } from "./types"
|
|
29
|
+
export { AppFieldError, firstFieldErrorMessage } from "./app-field-error"
|
|
30
|
+
export { useAppForm, appFormOptions } from "./create-app-form"
|
|
@@ -9,7 +9,14 @@ import { Route as HarnessRoute } from "@/routes/_nav/harness/route.lazy"
|
|
|
9
9
|
import type { GalleryItem } from "../gallery/gallery-types"
|
|
10
10
|
import { m } from "@/paraglide/messages.js"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
type VideoGenerationState = {
|
|
13
|
+
isActive: boolean
|
|
14
|
+
progress: number | undefined
|
|
15
|
+
status: string | null
|
|
16
|
+
start: (item: GalleryItem, referenceImage?: string) => boolean
|
|
17
|
+
stop: () => void
|
|
18
|
+
}
|
|
19
|
+
const { listGenerationRunsFn, createVariantFn, attachToStoryFn, attachToKbFn, attachPrototypeToDesignFn, listStoriesFn, listKnowledgeFn, listDesignsFn, videoGeneration } = vi.hoisted(() => ({
|
|
13
20
|
listGenerationRunsFn: vi.fn<() => Promise<{ runs: GalleryItem[]; nextCursor: string | null }>>(),
|
|
14
21
|
createVariantFn: vi.fn<() => Promise<{ runId: string; artifacts: unknown[]; images: unknown[] }>>(),
|
|
15
22
|
attachToStoryFn: vi.fn(),
|
|
@@ -18,9 +25,11 @@ const { listGenerationRunsFn, createVariantFn, attachToStoryFn, attachToKbFn, at
|
|
|
18
25
|
listStoriesFn: vi.fn(),
|
|
19
26
|
listKnowledgeFn: vi.fn(),
|
|
20
27
|
listDesignsFn: vi.fn(),
|
|
28
|
+
videoGeneration: vi.fn<() => VideoGenerationState>(),
|
|
21
29
|
}))
|
|
22
30
|
vi.mock("@/components/billing/feature-gate", () => ({ FeatureGate: ({ children }: { children: React.ReactNode }) => children }))
|
|
23
31
|
vi.mock("@/server/serverFns/harness/gallery", () => ({ listGenerationRunsFn, createVariantFn }))
|
|
32
|
+
vi.mock("@/components/harness/gallery/use-video-generation", () => ({ useVideoGeneration: videoGeneration }))
|
|
24
33
|
vi.mock("@/server/serverFns/harness/attach-to-story", () => ({ attachToStoryFn }))
|
|
25
34
|
vi.mock("@/server/serverFns/harness/attach-to-kb", () => ({ attachToKbFn }))
|
|
26
35
|
vi.mock("@/server/serverFns/harness/attach-prototype", () => ({ attachPrototypeToDesignFn }))
|
|
@@ -56,6 +65,8 @@ beforeEach(() => {
|
|
|
56
65
|
vi.mocked(listStoriesFn).mockReset()
|
|
57
66
|
vi.mocked(listKnowledgeFn).mockReset()
|
|
58
67
|
vi.mocked(listDesignsFn).mockReset()
|
|
68
|
+
videoGeneration.mockClear()
|
|
69
|
+
videoGeneration.mockImplementation(() => ({ isActive: false, progress: undefined, status: null, start: vi.fn(() => true), stop: vi.fn() }))
|
|
59
70
|
})
|
|
60
71
|
afterEach(() => { cleanup(); clients.splice(0).forEach((value) => value.clear()) })
|
|
61
72
|
|
|
@@ -118,6 +129,31 @@ describe("gallery behavior", () => {
|
|
|
118
129
|
expect(screen.getAllByRole("button", { name: "生成变体" })).toHaveLength(2)
|
|
119
130
|
})
|
|
120
131
|
|
|
132
|
+
it("shows streaming progress on the generating video card and stops from it", async () => {
|
|
133
|
+
const start = vi.fn(() => true)
|
|
134
|
+
const stop = vi.fn()
|
|
135
|
+
videoGeneration.mockImplementation(() => ({ isActive: true, progress: 42, status: "processing", start, stop }))
|
|
136
|
+
vi.mocked(listGenerationRunsFn).mockResolvedValue({ runs: [item("a"), item("b", "video")], nextCursor: null })
|
|
137
|
+
grid()
|
|
138
|
+
const stopButtons = await screen.findAllByRole("button", { name: m.gallery_videoStop() })
|
|
139
|
+
expect(stopButtons).toHaveLength(1) // 仅生成中的视频卡
|
|
140
|
+
expect(screen.getByText(m.gallery_videoProgress({ progress: 42 }))).toBeInTheDocument()
|
|
141
|
+
// 生成中的卡片不再提供变体入口,图片卡不受影响
|
|
142
|
+
expect(screen.getAllByRole("button", { name: m.gallery_createVariant() })).toHaveLength(1)
|
|
143
|
+
fireEvent.click(stopButtons[0])
|
|
144
|
+
expect(stop).toHaveBeenCalledTimes(1)
|
|
145
|
+
expect(start).not.toHaveBeenCalled()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it("routes video variant clicks to the streaming hook (toast covered by hook tests)", async () => {
|
|
149
|
+
const start = vi.fn((_item: GalleryItem, _referenceImage?: string) => false)
|
|
150
|
+
videoGeneration.mockImplementation(() => ({ isActive: false, progress: undefined, status: null, start, stop: vi.fn() }))
|
|
151
|
+
vi.mocked(listGenerationRunsFn).mockResolvedValue({ runs: [item("b", "video")], nextCursor: null })
|
|
152
|
+
grid()
|
|
153
|
+
fireEvent.click(await screen.findByRole("button", { name: m.gallery_createVariant() }))
|
|
154
|
+
expect(start.mock.calls[0]?.[0]).toMatchObject({ id: "b" })
|
|
155
|
+
})
|
|
156
|
+
|
|
121
157
|
it("keeps the gallery and localizes variant failures", async () => {
|
|
122
158
|
vi.mocked(listGenerationRunsFn).mockResolvedValue({ runs: [item("a")], nextCursor: null })
|
|
123
159
|
vi.mocked(createVariantFn).mockRejectedValue(new Error("provider failure"))
|
|
@@ -79,7 +79,10 @@ vi.mock("@tanstack/react-router", () => ({
|
|
|
79
79
|
),
|
|
80
80
|
}))
|
|
81
81
|
|
|
82
|
-
vi.mock("@tanstack/react-query", () => ({
|
|
82
|
+
vi.mock("@tanstack/react-query", async (importOriginal) => ({
|
|
83
|
+
// queryOptions 工厂(lib/queries/agents)经组件 import 链在此求值,
|
|
84
|
+
// 需要真实 queryOptions;useQuery 仍按本测试口径桩化
|
|
85
|
+
...(await importOriginal<typeof import("@tanstack/react-query")>()),
|
|
83
86
|
useQuery: () => ({ data: undefined }),
|
|
84
87
|
}))
|
|
85
88
|
|