@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
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useEffect, useRef, useState, type ComponentType } from "react"
|
|
19
|
+
import { useHotkeys } from "@tanstack/react-hotkeys"
|
|
19
20
|
import {
|
|
20
21
|
ArrowRight,
|
|
21
22
|
ChevronDown,
|
|
@@ -148,26 +149,19 @@ export function useCanvasDrawing(shapeVariant?: string | null) {
|
|
|
148
149
|
setTool(id)
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
e.preventDefault()
|
|
165
|
-
selectTool(id)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
document.addEventListener("keydown", handler)
|
|
169
|
-
return () => document.removeEventListener("keydown", handler)
|
|
170
|
-
}, [enabled])
|
|
152
|
+
// 收编手写 keydown(审计「canvas 工具单键」项):TOOL_DEFINITIONS 单键 →
|
|
153
|
+
// useHotkeys 数组注册(动态长度安全,不违反 hooks 规则)。enabled 门控、
|
|
154
|
+
// 输入框守卫(ignoreInputs 等价旧 isTypingTarget 判断,范围更宽更安全)、
|
|
155
|
+
// preventDefault 均由官方通道承担;无修饰键 chord 与旧「仅拦 ctrl/meta/alt、
|
|
156
|
+
// 放行 shift」的差异是官方精确匹配修饰键(shift+单键不再触发,Figma 同款语义)
|
|
157
|
+
useHotkeys(
|
|
158
|
+
TOOL_DEFINITIONS.flatMap((definition) =>
|
|
159
|
+
definition.hotkey
|
|
160
|
+
? [{ hotkey: definition.hotkey, callback: () => selectTool(definition.id) }]
|
|
161
|
+
: [],
|
|
162
|
+
),
|
|
163
|
+
{ enabled, ignoreInputs: true, preventDefault: true },
|
|
164
|
+
)
|
|
171
165
|
|
|
172
166
|
useEffect(() => {
|
|
173
167
|
if (!enabled || tool === "select") return
|
|
@@ -216,7 +210,10 @@ export function useCanvasDrawing(shapeVariant?: string | null) {
|
|
|
216
210
|
window.addEventListener("pointerdown", onPointerDown, true)
|
|
217
211
|
window.addEventListener("pointermove", onPointerMove)
|
|
218
212
|
window.addEventListener("dblclick", onDoubleClick)
|
|
219
|
-
// keydown 捕获相:同钢笔分支,先于 document
|
|
213
|
+
// keydown 捕获相:同钢笔分支,先于 document 冒泡相的删除监听。
|
|
214
|
+
// 豁免收编(audit 2026-10-31):捕获相时序敏感——绘制态的删除键必须先于
|
|
215
|
+
// useDeleteSelection(useHotkeys,document 冒泡相)拦截,官方 API 无
|
|
216
|
+
// 「绘制态条件捕获相」的表达,保持手写监听
|
|
220
217
|
window.addEventListener("keydown", onKeyDown, true)
|
|
221
218
|
return () => {
|
|
222
219
|
window.removeEventListener("pointerdown", onPointerDown, true)
|
|
@@ -272,7 +269,9 @@ export function useCanvasDrawing(shapeVariant?: string | null) {
|
|
|
272
269
|
window.addEventListener("dblclick", onDoubleClick)
|
|
273
270
|
// keydown 必须捕获相:useDeleteSelection 在 document 冒泡相(冒泡顺序
|
|
274
271
|
// document 先于 window),冒泡相注册的 stopPropagation 来不及拦截——
|
|
275
|
-
// 「选中 A → 切钢笔 → 绘制中按 Backspace」会同时删 A
|
|
272
|
+
// 「选中 A → 切钢笔 → 绘制中按 Backspace」会同时删 A 并回退锚点。
|
|
273
|
+
// 豁免收编(audit 2026-10-31):捕获相时序敏感,官方 API 无
|
|
274
|
+
// 「绘制态条件捕获相」的表达,保持手写监听
|
|
276
275
|
window.addEventListener("keydown", onKeyDown, true)
|
|
277
276
|
return () => {
|
|
278
277
|
window.removeEventListener("pointerdown", onPointerDown, true)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CanvasProvider loadDoc 回显豁免回归(任务 2 审查修复轮 2 / 缺陷③):
|
|
6
|
+
*
|
|
7
|
+
* engine.loadDoc → notifyDocChange → pushDoc → 父层 onDocChange(即
|
|
8
|
+
* useDesignAutoSave.handleDocChange → setCanvasDirty(true))——打开/切页后
|
|
9
|
+
* ~2s 内 canvasDirty 幻影 true,未保存 guard 误拦(旧 beforeunload 同样误报)。
|
|
10
|
+
* 修复:pushDoc 在 loadDoc 同步执行窗口内只刷新 store 快照,不通知 React
|
|
11
|
+
* 父层;窗口 = loadDoc 调用栈,用户事件无法插入(无吞真实编辑的竞态)。
|
|
12
|
+
*
|
|
13
|
+
* 断言落到「父层通知 → handleDocChange → canvasDirty」链(真实
|
|
14
|
+
* useDesignAutoSave + 真实 CanvasProvider 接线,LeaferEngine 以替身驱动)。
|
|
15
|
+
* 引擎本体在 jsdom 不可真实驱动(Leafer WebGL),按审查许可取 notify 层
|
|
16
|
+
* 断言——取舍:不覆盖引擎内部 applyScene 的通知次数,但豁免按「同步窗口」
|
|
17
|
+
* 而非一次性开关实现,窗口内任意次回显通知都被吞掉,不依赖次数假设。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, expect, it, vi, beforeEach } from "vitest"
|
|
21
|
+
import { renderHook, act } from "@testing-library/react"
|
|
22
|
+
import { render } from "@testing-library/react"
|
|
23
|
+
import { useContext, useState } from "react"
|
|
24
|
+
|
|
25
|
+
const engineSlots = vi.hoisted(() => ({
|
|
26
|
+
create: vi.fn(),
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
vi.mock("@/design-engine/engine/app", () => ({
|
|
30
|
+
LeaferEngine: {
|
|
31
|
+
create: (opts: unknown) => engineSlots.create(opts),
|
|
32
|
+
},
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
import { CanvasContext, CanvasProvider } from "../canvas-context"
|
|
36
|
+
import { LeaferEngine } from "@/design-engine/engine/app"
|
|
37
|
+
import { useDesignAutoSave } from "@/components/design/editor/layout/hooks/use-design-auto-save"
|
|
38
|
+
import { createEmptyDoc, createBoardNode, type SceneDocument } from "@/components/design/canvas/document/types"
|
|
39
|
+
import type { DocContent } from "@/components/design/editor/layout/design-editor-doc-utils"
|
|
40
|
+
import { updateDesignPageFn } from "@/server/serverFns/design/pages"
|
|
41
|
+
|
|
42
|
+
vi.mock("@/server/serverFns/design/pages", () => ({
|
|
43
|
+
updateDesignPageFn: vi.fn(async () => ({ id: "p1" })),
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
type HookArgs = Parameters<typeof useDesignAutoSave>[0]
|
|
47
|
+
|
|
48
|
+
const emptyScene: SceneDocument = createEmptyDoc("desktop")
|
|
49
|
+
|
|
50
|
+
function userScene(): SceneDocument {
|
|
51
|
+
return {
|
|
52
|
+
version: 1,
|
|
53
|
+
meta: { device: "desktop" },
|
|
54
|
+
nodes: [
|
|
55
|
+
createBoardNode("desktop"),
|
|
56
|
+
{
|
|
57
|
+
id: "n_1",
|
|
58
|
+
kind: "shape",
|
|
59
|
+
type: "rect",
|
|
60
|
+
parentId: "board",
|
|
61
|
+
x: 10,
|
|
62
|
+
y: 10,
|
|
63
|
+
width: 40,
|
|
64
|
+
height: 40,
|
|
65
|
+
style: {},
|
|
66
|
+
props: {},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
} as unknown as SceneDocument
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function baseArgs(over: Partial<HookArgs> = {}): HookArgs {
|
|
73
|
+
return {
|
|
74
|
+
activePageId: "page_1",
|
|
75
|
+
sceneDoc: emptyScene,
|
|
76
|
+
setSceneDoc: vi.fn(),
|
|
77
|
+
docContent: { html: "", markdown: "" },
|
|
78
|
+
setDocContent: vi.fn(),
|
|
79
|
+
canvasDirty: false,
|
|
80
|
+
setCanvasDirty: vi.fn(),
|
|
81
|
+
docDirty: false,
|
|
82
|
+
setDocDirty: vi.fn(),
|
|
83
|
+
isPreviewingRef: { current: false },
|
|
84
|
+
canvasLoadedRef: { current: true },
|
|
85
|
+
lastLoadedDocRef: { current: null },
|
|
86
|
+
timezone: "Asia/Shanghai",
|
|
87
|
+
setCanvasAutoSaveTime: vi.fn(),
|
|
88
|
+
setDocAutoSaveTime: vi.fn(),
|
|
89
|
+
docSaveFnRef: { current: async () => {} },
|
|
90
|
+
docDebouncer: { maybeExecute: vi.fn(), cancel: vi.fn() },
|
|
91
|
+
...over,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type FakeEngine = {
|
|
96
|
+
mount: ReturnType<typeof vi.fn>
|
|
97
|
+
dispose: ReturnType<typeof vi.fn>
|
|
98
|
+
remount: ReturnType<typeof vi.fn>
|
|
99
|
+
setKeyEventEnabled: ReturnType<typeof vi.fn>
|
|
100
|
+
getZoom: () => number
|
|
101
|
+
history: { canUndo: boolean; canRedo: boolean }
|
|
102
|
+
onDocChange: (cb: (doc: SceneDocument) => void) => () => void
|
|
103
|
+
onSelectionChange: (cb: unknown) => () => void
|
|
104
|
+
onToolChange: (cb: unknown) => () => void
|
|
105
|
+
loadDoc: (doc: SceneDocument) => void
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function installFakeEngine() {
|
|
109
|
+
let docListener: ((doc: SceneDocument) => void) | null = null
|
|
110
|
+
const loadDocSpy = vi.fn()
|
|
111
|
+
const fakeEngine = {
|
|
112
|
+
mount: vi.fn(),
|
|
113
|
+
dispose: vi.fn(),
|
|
114
|
+
remount: vi.fn(),
|
|
115
|
+
setKeyEventEnabled: vi.fn(),
|
|
116
|
+
getZoom: () => 1,
|
|
117
|
+
history: { canUndo: false, canRedo: false },
|
|
118
|
+
onDocChange: vi.fn((cb: (doc: SceneDocument) => void) => {
|
|
119
|
+
docListener = cb
|
|
120
|
+
return () => {}
|
|
121
|
+
}),
|
|
122
|
+
onSelectionChange: vi.fn(() => () => {}),
|
|
123
|
+
onToolChange: vi.fn(() => () => {}),
|
|
124
|
+
// 真实引擎 loadDoc 末尾同步 notifyDocChange(engine-doc.ts:164)——回显
|
|
125
|
+
loadDoc: vi.fn((doc: SceneDocument) => {
|
|
126
|
+
loadDocSpy(doc)
|
|
127
|
+
docListener?.(doc)
|
|
128
|
+
}),
|
|
129
|
+
}
|
|
130
|
+
engineSlots.create.mockResolvedValue(fakeEngine)
|
|
131
|
+
return { fakeEngine: fakeEngine as unknown as LeaferEngine & FakeEngine, notifyDoc: (doc: SceneDocument) => docListener?.(doc), loadDocSpy }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 把画布 host 挂进 provider(真实挂载链:attachHost → hostRef → create effect) */
|
|
135
|
+
function HostMount() {
|
|
136
|
+
const canvas = useContext(CanvasContext)
|
|
137
|
+
return <div ref={(node) => canvas?.attachHost(node)} data-testid="canvas-host" />
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
beforeEach(() => {
|
|
141
|
+
vi.clearAllMocks()
|
|
142
|
+
vi.mocked(updateDesignPageFn).mockClear()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe("CanvasProvider loadDoc 回显豁免(审查修复③)", () => {
|
|
146
|
+
it("loadDoc 回显(挂载 + 切页)不置 canvasDirty;真实用户编辑同步置 dirty", async () => {
|
|
147
|
+
const { notifyDoc, loadDocSpy } = installFakeEngine()
|
|
148
|
+
|
|
149
|
+
// hook 持真实 state(dirty 断言目标),非可 mock 的 setter
|
|
150
|
+
const { result } = renderHook(() => {
|
|
151
|
+
const [canvasDirty, setCanvasDirty] = useState(false)
|
|
152
|
+
const [docDirty, setDocDirty] = useState(false)
|
|
153
|
+
const [sceneDoc, setSceneDoc] = useState(emptyScene)
|
|
154
|
+
const [docContent, setDocContent] = useState<DocContent>({ html: "", markdown: "" })
|
|
155
|
+
return useDesignAutoSave({
|
|
156
|
+
...baseArgs(),
|
|
157
|
+
sceneDoc,
|
|
158
|
+
setSceneDoc,
|
|
159
|
+
docContent,
|
|
160
|
+
setDocContent,
|
|
161
|
+
canvasDirty,
|
|
162
|
+
setCanvasDirty,
|
|
163
|
+
docDirty,
|
|
164
|
+
setDocDirty,
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const view = render(
|
|
169
|
+
<CanvasProvider initialDoc={emptyScene} onDocChange={(doc) => result.current.handleDocChange(doc)}>
|
|
170
|
+
<HostMount />
|
|
171
|
+
</CanvasProvider>,
|
|
172
|
+
)
|
|
173
|
+
// flush 引擎 create promise → mount → loadDoc(挂载回显)
|
|
174
|
+
await act(async () => {})
|
|
175
|
+
|
|
176
|
+
expect(loadDocSpy).toHaveBeenCalledTimes(1)
|
|
177
|
+
// 关键断言①:挂载 loadDoc 回显未置 dirty(修复前此处 true——幻影 dirty)
|
|
178
|
+
expect(result.current.canvasDirty).toBe(false)
|
|
179
|
+
|
|
180
|
+
// 切页:initialDoc 换新 → engine.loadDoc(新页) → 回显
|
|
181
|
+
await act(async () => {
|
|
182
|
+
view.rerender(
|
|
183
|
+
<CanvasProvider initialDoc={userScene()} onDocChange={(doc) => result.current.handleDocChange(doc)}>
|
|
184
|
+
<HostMount />
|
|
185
|
+
</CanvasProvider>,
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
expect(loadDocSpy).toHaveBeenCalledTimes(2)
|
|
189
|
+
// 关键断言②:切页 loadDoc 回显同样不置 dirty
|
|
190
|
+
expect(result.current.canvasDirty).toBe(false)
|
|
191
|
+
|
|
192
|
+
// 真实用户编辑一笔(引擎编辑路径:load 窗口外的 docChanged notify)
|
|
193
|
+
await act(async () => {
|
|
194
|
+
notifyDoc(userScene())
|
|
195
|
+
})
|
|
196
|
+
// 关键断言③:真实编辑同步置 dirty(豁免不吞编辑)
|
|
197
|
+
expect(result.current.canvasDirty).toBe(true)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
@@ -789,7 +789,16 @@ describe("canvas hooks", () => {
|
|
|
789
789
|
await waitFor(() => expect(engine.mount).toHaveBeenCalledTimes(1))
|
|
790
790
|
expect(screen.getByTestId("pan-mode").textContent).toBe("false")
|
|
791
791
|
|
|
792
|
-
|
|
792
|
+
// 真实浏览器中 keydown 恒派发给聚焦元素(e.target === activeElement);
|
|
793
|
+
// 收编后的输入框门控走 typingFocus(focusin/focusout 事实源),故先
|
|
794
|
+
// 聚焦输入框再派发空格键
|
|
795
|
+
const input = screen.getByTestId("plain-input")
|
|
796
|
+
act(() => {
|
|
797
|
+
input.focus()
|
|
798
|
+
})
|
|
799
|
+
fireEvent.keyDown(input, { key: " " })
|
|
800
|
+
expect(screen.getByTestId("pan-mode").textContent).toBe("false")
|
|
801
|
+
fireEvent.keyUp(input, { key: " " })
|
|
793
802
|
expect(screen.getByTestId("pan-mode").textContent).toBe("false")
|
|
794
803
|
|
|
795
804
|
unmount()
|
|
@@ -808,7 +817,10 @@ describe("canvas hooks", () => {
|
|
|
808
817
|
Object.defineProperty(editable, "isContentEditable", { value: true, configurable: true })
|
|
809
818
|
document.body.appendChild(editable)
|
|
810
819
|
try {
|
|
811
|
-
|
|
820
|
+
// focus/blur 触发 focusin/focusout → CanvasProvider typingFocus setState,须包 act
|
|
821
|
+
act(() => {
|
|
822
|
+
editable.focus()
|
|
823
|
+
})
|
|
812
824
|
fireEvent.keyDown(editable, { key: " " })
|
|
813
825
|
expect(screen.getByTestId("pan-mode").textContent).toBe("false")
|
|
814
826
|
// keyUp 也不应触发(panMode 从未变为 true)
|
|
@@ -816,7 +828,9 @@ describe("canvas hooks", () => {
|
|
|
816
828
|
expect(screen.getByTestId("pan-mode").textContent).toBe("false")
|
|
817
829
|
} finally {
|
|
818
830
|
editable.remove()
|
|
819
|
-
|
|
831
|
+
act(() => {
|
|
832
|
+
if (document.activeElement instanceof HTMLElement) document.activeElement.blur()
|
|
833
|
+
})
|
|
820
834
|
}
|
|
821
835
|
|
|
822
836
|
unmount()
|
|
@@ -17,12 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
createContext,
|
|
20
|
+
useCallback,
|
|
20
21
|
useEffect,
|
|
21
22
|
useRef,
|
|
23
|
+
useState,
|
|
22
24
|
type ReactNode,
|
|
23
25
|
type RefObject,
|
|
24
26
|
} from "react"
|
|
25
27
|
|
|
28
|
+
import { useHotkey, useKeyHold } from "@tanstack/react-hotkeys"
|
|
29
|
+
|
|
26
30
|
import { isTypingTarget } from "../tools/dom"
|
|
27
31
|
import { LeaferEngine } from "@/design-engine/engine/app"
|
|
28
32
|
import { createEmptyDoc, type DeviceType, type SceneDocument, type ToolId } from "../document/types"
|
|
@@ -116,6 +120,21 @@ export function CanvasProvider({
|
|
|
116
120
|
const engineRef = useRef<LeaferEngine | null>(null)
|
|
117
121
|
const lastEmittedDocRef = useRef<SceneDocument | null>(null)
|
|
118
122
|
const selectionNotifyRafRef = useRef<number | null>(null)
|
|
123
|
+
// loadDoc 回显豁免(任务 2 审查修复③):loadDoc 末尾同步 notifyDocChange
|
|
124
|
+
// → pushDoc → 父层 onDocChange → handleDocChange 误置 canvasDirty(打开/
|
|
125
|
+
// 切页后幻影 dirty,未保存 guard 误拦)。窗口 = loadDoc 同步调用栈(用户
|
|
126
|
+
// 事件无法插入,无吞真实编辑竞态);窗口内 pushDoc 仍刷新 store 快照
|
|
127
|
+
//(lastEmittedDocRef 供 initialDoc effect 判等、doc 供画布消费),仅跳过
|
|
128
|
+
// 父层通知。trackChanges/编辑路径不受影响(发生在窗口外,照常同步置 dirty)。
|
|
129
|
+
const loadEchoGateRef = useRef(false)
|
|
130
|
+
const loadDocSilently = useCallback((eng: LeaferEngine, doc: SceneDocument): void => {
|
|
131
|
+
loadEchoGateRef.current = true
|
|
132
|
+
try {
|
|
133
|
+
eng.loadDoc(doc)
|
|
134
|
+
} finally {
|
|
135
|
+
loadEchoGateRef.current = false
|
|
136
|
+
}
|
|
137
|
+
}, [])
|
|
119
138
|
|
|
120
139
|
flagsRef.current = { readOnly }
|
|
121
140
|
callbacksRef.current = { initialDoc, onDocChange }
|
|
@@ -182,7 +201,10 @@ export function CanvasProvider({
|
|
|
182
201
|
lastEmittedDocRef.current = doc
|
|
183
202
|
state.doc = doc
|
|
184
203
|
if (doc.meta?.device) state.device = doc.meta.device
|
|
185
|
-
|
|
204
|
+
// loadDoc 回显窗口内只刷新快照、不通知父层(注释见 loadEchoGateRef)
|
|
205
|
+
if (!loadEchoGateRef.current) {
|
|
206
|
+
callbacksRef.current.onDocChange?.(doc)
|
|
207
|
+
}
|
|
186
208
|
refreshMetrics()
|
|
187
209
|
rebuild()
|
|
188
210
|
},
|
|
@@ -248,7 +270,7 @@ export function CanvasProvider({
|
|
|
248
270
|
created.onToolChange(store.pushTool),
|
|
249
271
|
)
|
|
250
272
|
const initial = callbacksRef.current.initialDoc
|
|
251
|
-
if (initial) created
|
|
273
|
+
if (initial) loadDocSilently(created, initial)
|
|
252
274
|
store.attach(created)
|
|
253
275
|
// 诊断埋点:dev 环境暴露引擎引用,供真实页面逐层调试命中/选中链路
|
|
254
276
|
if (import.meta.env.DEV) {
|
|
@@ -298,30 +320,24 @@ export function CanvasProvider({
|
|
|
298
320
|
const eng = engineRef.current
|
|
299
321
|
if (!eng || !initialDoc) return
|
|
300
322
|
if (initialDoc === lastEmittedDocRef.current) return
|
|
301
|
-
eng
|
|
302
|
-
}, [initialDoc])
|
|
323
|
+
loadDocSilently(eng, initialDoc)
|
|
324
|
+
}, [initialDoc, loadDocSilently])
|
|
303
325
|
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
//
|
|
326
|
+
// 空格键(收编手写 keydown/keyup,审计「空格 pan」项):按住进入 pan 模式
|
|
327
|
+
//(手型光标 + 左键可拖动),松开退出;与右键拖动并列,任一持有 panMode 即 true。
|
|
328
|
+
// hold 状态来自官方 KeyStateTracker(useKeyHold("Space")——tracker 归一化
|
|
329
|
+
// " "→"Space",勿传字面空格),窗口失焦自动清位(旧实现 keyup 丢失会卡 pan)。
|
|
330
|
+
// tracker 无输入框概念,输入框聚焦不响应由下方 typingFocus 门控承担。
|
|
331
|
+
// 官方 API 缺口:useKeyHold 不做 preventDefault,空格的页面滚动/焦点控件
|
|
332
|
+
// 激活抑制由并行 "Space" chord 注册(preventDefault)承担;官方对聚焦
|
|
333
|
+
// button/link 的空格激活按 ignoreInputs 跳过(无障碍语义,旧实现一律拦死,
|
|
334
|
+
// 此处为对齐官方语义的有意放宽)。
|
|
335
|
+
const spaceHeld = useKeyHold("Space")
|
|
336
|
+
const [typingFocus, setTypingFocus] = useState(false)
|
|
307
337
|
useEffect(() => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
store.setSpaceHeld(true)
|
|
312
|
-
e.preventDefault()
|
|
313
|
-
}
|
|
314
|
-
const onKeyUp = (e: KeyboardEvent) => {
|
|
315
|
-
if (e.key !== " ") return
|
|
316
|
-
store.setSpaceHeld(false)
|
|
317
|
-
}
|
|
318
|
-
document.addEventListener("keydown", onKeyDown)
|
|
319
|
-
document.addEventListener("keyup", onKeyUp)
|
|
320
|
-
return () => {
|
|
321
|
-
document.removeEventListener("keydown", onKeyDown)
|
|
322
|
-
document.removeEventListener("keyup", onKeyUp)
|
|
323
|
-
}
|
|
324
|
-
}, [store])
|
|
338
|
+
store.setSpaceHeld(spaceHeld && !typingFocus)
|
|
339
|
+
}, [spaceHeld, typingFocus, store])
|
|
340
|
+
useHotkey("Space", () => {}, { ignoreInputs: true, preventDefault: true })
|
|
325
341
|
|
|
326
342
|
// 输入框聚焦时关闭 leafer-editor 的键盘监听(leafer-editor 官方方案)。
|
|
327
343
|
// 从 leafer-editor 2.3.0 源码定位:
|
|
@@ -339,12 +355,17 @@ export function CanvasProvider({
|
|
|
339
355
|
return isTypingTarget(el as HTMLElement | null)
|
|
340
356
|
}
|
|
341
357
|
const onDocFocus = (): void => {
|
|
342
|
-
|
|
358
|
+
const inside = isInsideTyping()
|
|
359
|
+
// 焦点位置提升为 React 状态:空格 pan 的 typingFocus 门控复用同一事实源
|
|
360
|
+
setTypingFocus(inside)
|
|
361
|
+
engineRef.current?.setKeyEventEnabled(!inside)
|
|
343
362
|
}
|
|
344
363
|
document.addEventListener("focusin", onDocFocus)
|
|
345
364
|
document.addEventListener("focusout", onDocFocus)
|
|
346
365
|
// 初始状态同步一次(防止 mount 时焦点已在输入框)
|
|
347
|
-
|
|
366
|
+
const initialInside = isInsideTyping()
|
|
367
|
+
setTypingFocus(initialInside)
|
|
368
|
+
engineRef.current?.setKeyEventEnabled(!initialInside)
|
|
348
369
|
return () => {
|
|
349
370
|
document.removeEventListener("focusin", onDocFocus)
|
|
350
371
|
document.removeEventListener("focusout", onDocFocus)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useCallback, useEffect, type RefObject } from "react"
|
|
19
|
-
import { useHotkey } from "@tanstack/react-hotkeys"
|
|
19
|
+
import { useHotkey, useHotkeys } from "@tanstack/react-hotkeys"
|
|
20
20
|
|
|
21
21
|
import { toast } from "@/components/ui/toast"
|
|
22
22
|
import { m } from "@/paraglide/messages.js"
|
|
@@ -158,19 +158,17 @@ export function useDeleteSelection() {
|
|
|
158
158
|
const engine = snapshot.engine
|
|
159
159
|
const enabled = Boolean(engine) && !snapshot.readOnly
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
engine
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return () => document.removeEventListener("keydown", handler)
|
|
173
|
-
}, [enabled, engine])
|
|
161
|
+
// 收编手写 keydown(审计第 4 处):Delete/Backspace 删除选中,改官方
|
|
162
|
+
// useHotkeys 双 chord——enabled 门控、ignoreInputs 输入框守卫、preventDefault
|
|
163
|
+
// 均由官方通道承担。仍为 document 冒泡相(useHotkeys 默认 target=document),
|
|
164
|
+
// toolbar 绘制中的捕获相 stopPropagation 拦截先于本注册生效,时序与旧实现一致
|
|
165
|
+
useHotkeys(
|
|
166
|
+
[
|
|
167
|
+
{ hotkey: "Delete", callback: () => engine?.deleteSelectedNodes() },
|
|
168
|
+
{ hotkey: "Backspace", callback: () => engine?.deleteSelectedNodes() },
|
|
169
|
+
],
|
|
170
|
+
{ enabled, ignoreInputs: true, preventDefault: true },
|
|
171
|
+
)
|
|
174
172
|
}
|
|
175
173
|
|
|
176
174
|
// 编组/解组快捷键(FS-4.2):Mod+G 建组、Shift+Mod+G 解组;
|
|
@@ -252,34 +250,39 @@ export function useEditCommands(): void {
|
|
|
252
250
|
}
|
|
253
251
|
|
|
254
252
|
// 画布剪贴板快捷键:mod+A 全选 / mod+X 剪切 / mod+C 复制 / mod+V 粘贴;
|
|
255
|
-
//
|
|
253
|
+
// 输入框聚焦时不拦截(属文本编辑器自身行为)。
|
|
254
|
+
// 收编手写 keydown(审计「Mod+C」项):改官方 useHotkeys 四 chord 注册,
|
|
255
|
+
// enabled 门控、ignoreInputs 守卫、preventDefault 拦截浏览器默认行为均由
|
|
256
|
+
// 官方通道承担
|
|
256
257
|
export function useCanvasClipboard(): void {
|
|
257
258
|
const snapshot = useCanvasSnapshot()
|
|
258
259
|
const engine = snapshot.engine
|
|
259
260
|
const enabled = Boolean(engine) && !snapshot.readOnly
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}, [enabled, engine])
|
|
262
|
+
const clipboardEdit = useCallback(
|
|
263
|
+
(op: "selectAll" | "cutSelection" | "copySelection" | "pasteClipboard") => {
|
|
264
|
+
engine?.[op]()
|
|
265
|
+
},
|
|
266
|
+
[engine],
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
useHotkeys(
|
|
270
|
+
[
|
|
271
|
+
{ hotkey: "Mod+A", callback: () => clipboardEdit("selectAll") },
|
|
272
|
+
{ hotkey: "Mod+X", callback: () => clipboardEdit("cutSelection") },
|
|
273
|
+
{ hotkey: "Mod+C", callback: () => clipboardEdit("copySelection") },
|
|
274
|
+
{ hotkey: "Mod+V", callback: () => clipboardEdit("pasteClipboard") },
|
|
275
|
+
],
|
|
276
|
+
{ enabled, ignoreInputs: true, preventDefault: true },
|
|
277
|
+
)
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
// Esc 逐级取消(Figma/Sketch 惯例):路径锚点编辑 → 退出编辑;绘制工具 →
|
|
281
281
|
// 退回选择工具;选择模式 → 清空选中(全选/多选/单选一律取消)。钢笔绘制中
|
|
282
|
-
// 的 Esc 由 toolbar 的捕获相处理(结束路径 +
|
|
282
|
+
// 的 Esc 由 toolbar 的捕获相处理(结束路径 + 退工具),此处冒泡相同事件幂等。
|
|
283
|
+
// 豁免收编(audit 2026-10-31「捕获相时序敏感」类):三态分支依赖 engine/工具
|
|
284
|
+
// 状态的判断顺序,且收编即与全局 close-panel(Escape)形成同 chord 运行时
|
|
285
|
+
// 注册冲突(findHotkeyConflicts 会告警)——保持手写监听
|
|
283
286
|
export function useEscapeTool() {
|
|
284
287
|
const { tool, setTool } = useTool()
|
|
285
288
|
const engine = useCanvasSnapshotOptional()?.engine ?? null
|
|
@@ -309,7 +312,9 @@ export function useEscapeTool() {
|
|
|
309
312
|
}, [tool, setTool, engine])
|
|
310
313
|
}
|
|
311
314
|
|
|
312
|
-
// Enter 进入/退出路径锚点编辑(单选 path/pen-path 时;Sketch
|
|
315
|
+
// Enter 进入/退出路径锚点编辑(单选 path/pen-path 时;Sketch 惯例)。
|
|
316
|
+
// 豁免收编:Enter 是输入提交语义主键(表单/对话框/测试清单 case-checklist
|
|
317
|
+
// 同用 Enter),收编即扩大同 chord 运行时冲突面,收益低——保持手写监听
|
|
313
318
|
export function usePathEditHotkey() {
|
|
314
319
|
const engine = useCanvasSnapshotOptional()?.engine ?? null
|
|
315
320
|
const selection = useCanvasSnapshotOptional()?.selection ?? []
|
|
@@ -53,6 +53,7 @@ vi.mock('@/components/design/canvas/plugins/presence/presence-layer', () => ({
|
|
|
53
53
|
|
|
54
54
|
vi.mock('@tanstack/react-hotkeys', () => ({
|
|
55
55
|
useHotkey: () => {},
|
|
56
|
+
useAppHotkeys: () => appHotkeys,
|
|
56
57
|
}))
|
|
57
58
|
|
|
58
59
|
vi.mock('@/components/design/editor/layout/design-left-sidebar', () => ({
|
|
@@ -100,7 +101,23 @@ vi.mock('@/server/serverFns/design/designs', () => ({
|
|
|
100
101
|
updateDesignFn: vi.fn(),
|
|
101
102
|
}))
|
|
102
103
|
|
|
104
|
+
// DesignEditorLayout 挂载 use-unsaved-guard(任务 2):无 RouterProvider 的
|
|
105
|
+
// 单测环境把 useBlocker 替换为 idle resolver(真实 blocker 注册属 router
|
|
106
|
+
// 集成行为,见 hooks/__tests__/use-unsaved-guard.test.tsx)
|
|
107
|
+
vi.mock('@tanstack/react-router', async (importOriginal) => ({
|
|
108
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
109
|
+
useBlocker: () => ({
|
|
110
|
+
status: 'idle' as const,
|
|
111
|
+
current: undefined,
|
|
112
|
+
next: undefined,
|
|
113
|
+
action: undefined,
|
|
114
|
+
proceed: undefined,
|
|
115
|
+
reset: undefined,
|
|
116
|
+
}),
|
|
117
|
+
}))
|
|
118
|
+
|
|
103
119
|
import { DesignEditorLayout } from '../design-editor-layout'
|
|
120
|
+
import { appHotkeys } from '@/lib/hotkeys/registry'
|
|
104
121
|
import type { Design } from '@allbluecn/kernel'
|
|
105
122
|
|
|
106
123
|
const designFixture = {
|
|
@@ -28,6 +28,7 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
|
28
28
|
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
|
|
29
29
|
|
|
30
30
|
import { CanvasContextMenu } from "../context-menu";
|
|
31
|
+
import { appHotkeys } from "@/lib/hotkeys/registry";
|
|
31
32
|
import { buildContextMenuItems, type ContextMenuItem } from "../context-menu-items";
|
|
32
33
|
import { DesignEditorLayout } from "../design-editor-layout";
|
|
33
34
|
import type { Design } from "@allbluecn/kernel";
|
|
@@ -105,7 +106,22 @@ vi.mock("@/server/serverFns/design/pages", () => ({
|
|
|
105
106
|
vi.mock("@/server/serverFns/design/designs", () => ({
|
|
106
107
|
updateDesignFn: vi.fn(() => Promise.resolve({})),
|
|
107
108
|
}));
|
|
108
|
-
vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {} }));
|
|
109
|
+
vi.mock("@tanstack/react-hotkeys", () => ({ useHotkey: () => {}, useAppHotkeys: () => appHotkeys }));
|
|
110
|
+
|
|
111
|
+
// DesignEditorLayout 挂载 use-unsaved-guard(任务 2):无 RouterProvider 的
|
|
112
|
+
// 单测环境把 useBlocker 替换为 idle resolver(真实 blocker 注册属 router
|
|
113
|
+
// 集成行为,见 hooks/__tests__/use-unsaved-guard.test.tsx)
|
|
114
|
+
vi.mock("@tanstack/react-router", async (importOriginal) => ({
|
|
115
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
116
|
+
useBlocker: () => ({
|
|
117
|
+
status: "idle" as const,
|
|
118
|
+
current: undefined,
|
|
119
|
+
next: undefined,
|
|
120
|
+
action: undefined,
|
|
121
|
+
proceed: undefined,
|
|
122
|
+
reset: undefined,
|
|
123
|
+
}),
|
|
124
|
+
}));
|
|
109
125
|
|
|
110
126
|
|
|
111
127
|
describe("CanvasContextMenu 组件", () => {
|
|
@@ -21,6 +21,22 @@ vi.mock("@tanstack/react-hotkeys", () => ({
|
|
|
21
21
|
useHotkey: (_key: string, handler: () => void) => {
|
|
22
22
|
hotkeyHandler = handler;
|
|
23
23
|
},
|
|
24
|
+
useAppHotkeys: () => appHotkeys,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// DesignEditorLayout 挂载 use-unsaved-guard(任务 2):无 RouterProvider 的
|
|
28
|
+
// 单测环境把 useBlocker 替换为 idle resolver(真实 blocker 注册属 router
|
|
29
|
+
// 集成行为,见 hooks/__tests__/use-unsaved-guard.test.tsx)
|
|
30
|
+
vi.mock("@tanstack/react-router", async (importOriginal) => ({
|
|
31
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
32
|
+
useBlocker: () => ({
|
|
33
|
+
status: "idle" as const,
|
|
34
|
+
current: undefined,
|
|
35
|
+
next: undefined,
|
|
36
|
+
action: undefined,
|
|
37
|
+
proceed: undefined,
|
|
38
|
+
reset: undefined,
|
|
39
|
+
}),
|
|
24
40
|
}));
|
|
25
41
|
|
|
26
42
|
// 单机模式前提钉住(任务 30 装配后布局按 isCollabEnabled 门控保存让位):
|
|
@@ -99,6 +115,7 @@ import { toast } from "@/components/ui/toast";
|
|
|
99
115
|
import { getDesignPageFn, updateDesignPageFn } from "@/server/serverFns/design/pages";
|
|
100
116
|
import { m } from "@/paraglide/messages.js";
|
|
101
117
|
import { DesignEditorLayout } from "../design-editor-layout";
|
|
118
|
+
import { appHotkeys } from "@/lib/hotkeys/registry";
|
|
102
119
|
import type { Design } from "@allbluecn/kernel";
|
|
103
120
|
|
|
104
121
|
const designFixture = {
|