refactor(app): 拆分 App 组合层的 surface 与协调 hook - #259
Merged
Conversation
Split App.tsx's leaf responsibilities into feature-owned boundaries and app-level coordination hooks, keeping behaviour byte-for-byte identical. App.tsx: 3265 -> 2532 lines. Surfaces (lazy boundaries move from app into the owning feature): - library: SidebarContextMenu, LibraryImportDialogs, LibraryMaintenanceDialogs, ProjectDraftDialogsHost - publishing: DocumentPublishingDialogs, PublishingTargetDialog, ProjectPublishingSettingsHost - assistant/editor/media/settings: AiAssistantPanelHost, DocumentPropertyManagerDialogHost, ImageSourceDialogHost, SettingsDialogHost, KeyboardShortcutsDialogHost - app/lazySurfaces.ts keeps only the first-paint editor canvas and DEV galleries Coordination hooks: - app: useManualDocumentSave, useGlobalSearchNavigation, useAiActionTargetNavigation, useNativeMenuBindings - features: useSheetSelection, useLiveDocumentProjection, useAiContentGenerators - workspaceSelection gains resolveGlobalSearchNavigationTarget as a pure rule Boundaries chosen deliberately: - LibraryImportDialogs (Markdown import + quick capture) is shared with the onboarding screen; LibraryMaintenanceDialogs (move / image cleanup / trash confirmations) mounts only in the main workspace, so onboarding no longer depends on the delete and cleanup state tree. - useSheetSelection is called before useWorkspaceNavigation so the selection pruning effect keeps running ahead of the repair effects, preserving the pre-refactor effect order; the forward reference to selectSheet uses the existing ref-latest paradigm. Tooling: - scripts/project-health.mjs is a read-only report split into collectHealthReport (pure) and renderHealthReport (presentation); its test asserts structured results and runs with test:release. It is a report, not a gate, and never fails npm run check. - vitest declares IS_REACT_ACT_ENVIRONMENT via src/testSetup.ts - shadcn moves to devDependencies (CLI only, no runtime import) Verified: check:architecture, tsc, eslint --max-warnings=0, prettier, cargo fmt, vitest (235 files / 1101 tests), test:release, build:web + check:bundle (1131.8 KiB initial, +0.2 KiB; all lazy chunks still split). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
做了什么
把
App.tsx的叶子职责拆到各 feature 自己的边界,并把跨功能协调抽成 app 级 hook。行为逐字保持不变。src/app/App.tsx:3265 → 2532 行Surface(lazy 边界从 app 下沉到所属 feature)
SidebarContextMenu、LibraryImportDialogs、LibraryMaintenanceDialogs、ProjectDraftDialogsHostDocumentPublishingDialogs、PublishingTargetDialog、ProjectPublishingSettingsHostAiAssistantPanelHost、DocumentPropertyManagerDialogHost、ImageSourceDialogHost、SettingsDialogHost、KeyboardShortcutsDialogHostapp/lazySurfaces.ts只保留首屏编辑器画布与 DEV 设计画廊,不再充当全局 surface registry。协调 hook
useManualDocumentSave、useGlobalSearchNavigation、useAiActionTargetNavigation、useNativeMenuBindingsuseSheetSelection、useLiveDocumentProjection、useAiContentGeneratorsworkspaceSelection新增纯规则resolveGlobalSearchNavigationTarget两处刻意的边界选择
1. 引导页不再依赖删除状态树
写作库弹窗按“是否已有内容”拆成两个:
LibraryImportDialogs(Markdown 导入 + 快速记录)→ onboarding 首屏与主界面共用LibraryMaintenanceDialogs(移动、图片清理、项目/分组/文稿/废纸篓确认)→ 仅主界面否则引导页会挂上“清空废纸篓”这类它根本不该存在的路径。
2. effect 执行顺序与重构前逐字一致
useSheetSelection在useWorkspaceNavigation之前调用,保证“选择裁剪”effect 仍先于 repair effect 运行——否则删除文稿/切换写作库时会多一帧选中态闪烁。对selectSheet的前向依赖使用仓库已有的 ref-latest 范式(同openMarkdownImportRef),不是新发明。工程工具
scripts/project-health.mjs是只读报告,不是门禁:按 SRP 拆成collectHealthReport(纯统计)+renderHealthReport(呈现);测试断言结构化结果而非控制台文案,随test:release执行,永远不会让npm run check变红vitest通过src/testSetup.ts声明IS_REACT_ACT_ENVIRONMENTshadcn移到devDependencies(纯 CLI,零运行时引用;CI 用npm ci --legacy-peer-deps仍会安装)验证
check:architecturetsc --noEmiteslint --max-warnings=0prettier --check+cargo fmt --checkvitest runtest:releasebuild:web+check:bundle搬运保真度做了逐字比对:右键菜单 240 行 JSX 的全部中文标签与分支条件不变;
resolveGlobalSearchNavigationTarget的requestListScroll与旧内联逻辑等价。ConfirmDialog/MoveSheetDialog/QuickCaptureDialog/UnusedImageCleanupDialog/SettingsDialog/AiAssistantPanel等 lazy chunk 全部保持独立分包,code-splitting 未退化。已知的后续工作(本 PR 不做)
createLazySurface工厂useAiActionTargetNavigation/useManualDocumentSave的useCallback依赖里含每次都变的数组与内联箭头,memo 永不命中 → 应抽useEventCallback统一 ref-latest 范式(会连带影响useAppShortcuts,风险面不同,单独提交)useState构成的跨功能状态所有权一根没动。下一刀该切状态,不该切 JSX。🤖 Generated with Claude Code