From 78a163a0c9dc1f03826fcbe87cf90969166e267e Mon Sep 17 00:00:00 2001 From: hugo Date: Sat, 21 Feb 2026 18:13:41 +0100 Subject: [PATCH] chore: phase 4 refactor --- TODO.md | 7 +- src/renderer/components/Editor/Editor.tsx | 155 +++--------------- src/renderer/navigation/editorRouting.ts | 60 +++++++ .../renderer/navigation/editorRouting.test.ts | 63 +++++++ 4 files changed, 149 insertions(+), 136 deletions(-) create mode 100644 src/renderer/navigation/editorRouting.ts create mode 100644 tests/renderer/navigation/editorRouting.test.ts diff --git a/TODO.md b/TODO.md index 1f03251..115dabb 100644 --- a/TODO.md +++ b/TODO.md @@ -213,8 +213,8 @@ Pure tab-policy helpers that decide: - `titleStrategy`: shared title resolver hooks ## Phase 4 — Editor Routing Registry -- Replace long `if/else` tab-type rendering chain with declarative tab->component mapping. -- Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing). +- [x] Replace long `if/else` tab-type rendering chain with declarative tab->component mapping. +- [x] Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing). ## Phase 5 — Menu/Event Harmonization - Route menu-driven view/tab actions through the same behavior layer where applicable. @@ -258,4 +258,5 @@ Pure tab-policy helpers that decide: - [x] Phase 3 continuation slice complete: migrated post/media open paths in Sidebar, Editor, LinkedMediaPanel, and Panel to shared entity-tab policy. - [x] Phase 3 completion slice: centralized `chat` and `import` tab-open specs and migrated sidebar call sites. - [x] Phase 3 completion slice: centralized `git-diff` file/commit ID/spec/open helpers and reused shared parsing in `TabBar`. -- [ ] Next implementation slice: Phase 4 (editor routing registry). +- [x] Phase 4 complete: introduced `editorRouting` registry/resolver and migrated `Editor` to declarative route->view rendering. +- [ ] Next implementation slice: Phase 5 (menu/event harmonization). diff --git a/src/renderer/components/Editor/Editor.tsx b/src/renderer/components/Editor/Editor.tsx index aeb91c7..9bc0c0f 100644 --- a/src/renderer/components/Editor/Editor.tsx +++ b/src/renderer/components/Editor/Editor.tsx @@ -22,6 +22,7 @@ import { AutoSaveManager, getContrastColor } from '../../utils'; import { InsertModal } from '../InsertModal'; import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal'; import { openEntityTab } from '../../navigation/tabPolicy'; +import { EditorRoute, resolveEditorRoute } from '../../navigation/editorRouting'; import { useI18n } from '../../i18n'; import './Editor.css'; @@ -1724,20 +1725,7 @@ export const Editor: React.FC = () => { // Get the active tab const activeTab = tabs.find(t => t.id === activeTabId); - // Determine what to show based on active tab - // Settings and tags should only show when their tab is active, not based on activeView - // (activeView controls the sidebar, not the main content area) - const showPost = activeTab?.type === 'post'; - const showMedia = activeTab?.type === 'media'; - const showSettings = activeTab?.type === 'settings'; - const showStyle = activeTab?.type === 'style'; - const showTags = activeTab?.type === 'tags'; - const showChat = activeTab?.type === 'chat'; - const showImport = activeTab?.type === 'import'; - const showMetadataDiff = activeTab?.type === 'metadata-diff'; - const showGitDiff = activeTab?.type === 'git-diff'; - const showDocumentation = activeTab?.type === 'documentation'; - const showSiteValidation = activeTab?.type === 'site-validation'; + const editorRoute = resolveEditorRoute(activeTab); useEffect(() => { const activePostId = activeTab?.type === 'post' ? activeTab.id : null; @@ -1789,129 +1777,30 @@ export const Editor: React.FC = () => { ); - // Show settings only if settings tab is active - if (showSettings) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } + const editorViewRenderers: Record React.ReactNode> = { + settings: () => , + style: () => , + tags: () => , + chat: () => (editorRoute.tabId ? : ), + import: () => + editorRoute.tabId ? : , + 'metadata-diff': () => , + 'git-diff': () => + editorRoute.tabId && editorRoute.gitDiffResource + ? + : , + documentation: () => , + 'site-validation': () => , + post: () => (editorRoute.tabId ? : ), + media: () => (editorRoute.tabId ? : ), + dashboard: () => , + }; - if (showStyle) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } + const editorContent = editorViewRenderers[editorRoute.route](); - // Show tags if tags tab is active - if (showTags) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // Show chat if chat tab is active - if (showChat && activeTabId) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // Show import analysis if import tab is active - if (showImport && activeTabId) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // Show metadata diff if metadata-diff tab is active - if (showMetadataDiff) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // Show git diff view if git-diff tab is active - if (showGitDiff && activeTabId) { - const filePath = activeTabId.startsWith('git-diff:') ? activeTabId.slice('git-diff:'.length) : activeTabId; - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - if (showDocumentation) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - if (showSiteValidation) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // Show post editor if a post tab is active - if (showPost && activeTabId) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // Show media editor if a media tab is active - if (showMedia && activeTabId) { - return ( -
- - {renderErrorModal()} - {renderConfirmDeleteModal()} -
- ); - } - - // No tab active - show dashboard return (
- + {editorContent} {renderErrorModal()} {renderConfirmDeleteModal()}
diff --git a/src/renderer/navigation/editorRouting.ts b/src/renderer/navigation/editorRouting.ts new file mode 100644 index 0000000..5812ef4 --- /dev/null +++ b/src/renderer/navigation/editorRouting.ts @@ -0,0 +1,60 @@ +import type { Tab, TabType } from '../store/appStore'; +import { parseGitDiffTabId } from './tabPolicy'; + +export type EditorRoute = + | 'dashboard' + | 'post' + | 'media' + | 'settings' + | 'style' + | 'tags' + | 'chat' + | 'import' + | 'metadata-diff' + | 'git-diff' + | 'documentation' + | 'site-validation'; + +export const EDITOR_TAB_ROUTE_REGISTRY: Record> = { + post: 'post', + media: 'media', + settings: 'settings', + style: 'style', + tags: 'tags', + chat: 'chat', + import: 'import', + 'metadata-diff': 'metadata-diff', + 'git-diff': 'git-diff', + documentation: 'documentation', + 'site-validation': 'site-validation', +}; + +export interface EditorRouteResolution { + route: EditorRoute; + tabId: string | null; + gitDiffResource: string | null; +} + +export function resolveEditorRoute(activeTab: Tab | undefined): EditorRouteResolution { + if (!activeTab) { + return { + route: 'dashboard', + tabId: null, + gitDiffResource: null, + }; + } + + if (activeTab.type === 'git-diff') { + return { + route: 'git-diff', + tabId: activeTab.id, + gitDiffResource: parseGitDiffTabId(activeTab.id).resource, + }; + } + + return { + route: EDITOR_TAB_ROUTE_REGISTRY[activeTab.type], + tabId: activeTab.id, + gitDiffResource: null, + }; +} diff --git a/tests/renderer/navigation/editorRouting.test.ts b/tests/renderer/navigation/editorRouting.test.ts new file mode 100644 index 0000000..86c85d8 --- /dev/null +++ b/tests/renderer/navigation/editorRouting.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; +import type { Tab } from '../../../src/renderer/store/appStore'; +import { + EDITOR_TAB_ROUTE_REGISTRY, + resolveEditorRoute, +} from '../../../src/renderer/navigation/editorRouting'; + +describe('editorRouting', () => { + it('defines canonical tab-type to editor-route mapping', () => { + expect(EDITOR_TAB_ROUTE_REGISTRY).toEqual({ + post: 'post', + media: 'media', + settings: 'settings', + style: 'style', + tags: 'tags', + chat: 'chat', + import: 'import', + 'metadata-diff': 'metadata-diff', + 'git-diff': 'git-diff', + documentation: 'documentation', + 'site-validation': 'site-validation', + }); + }); + + it('resolves dashboard route when no active tab is present', () => { + expect(resolveEditorRoute(undefined)).toEqual({ + route: 'dashboard', + tabId: null, + gitDiffResource: null, + }); + }); + + it('resolves post and media routes with active tab id', () => { + const postTab: Tab = { type: 'post', id: 'post-1', isTransient: true }; + const mediaTab: Tab = { type: 'media', id: 'media-1', isTransient: false }; + + expect(resolveEditorRoute(postTab)).toEqual({ + route: 'post', + tabId: 'post-1', + gitDiffResource: null, + }); + + expect(resolveEditorRoute(mediaTab)).toEqual({ + route: 'media', + tabId: 'media-1', + gitDiffResource: null, + }); + }); + + it('resolves git-diff route and extracts resource from tab id', () => { + const tab: Tab = { + type: 'git-diff', + id: 'git-diff:commit:abc123', + isTransient: true, + }; + + expect(resolveEditorRoute(tab)).toEqual({ + route: 'git-diff', + tabId: 'git-diff:commit:abc123', + gitDiffResource: 'commit:abc123', + }); + }); +});