refactor(x): collapse the 12 view booleans in App.tsx onto ViewState - #794
Open
kwp3 wants to merge 1 commit into
Open
refactor(x): collapse the 12 view booleans in App.tsx onto ViewState#794kwp3 wants to merge 1 commit into
kwp3 wants to merge 1 commit into
Conversation
App.tsx modelled "which view is showing" twice: correctly as the ViewState union (used only for back/forward history and deep links), and badly as 12 mutually-exclusive useState booleans hand-synced across ~700 setter calls, plus 13 sites that enumerated the whole set in a &&/|| chain. The render is a ternary chain, so two-true silently rendered the first match and all-false rendered a blank pane. Neither errored. There is now one useState<ViewState>, and every is*Open flag plus selectedPath and selectedBackgroundTask are derived from it, so both states are unrepresentable. Readers are untouched; the setters collapse to 24 setView calls, and the 13 enumerating guards become three named predicates. isTabbedView and isTabbedViewExceptCode differ only by the Code view, which several guards predate - folding them together would change behaviour. isBrowserOpen stays a separate boolean: it overlays whatever view is showing rather than being one. workspaceInitialPath, knowledgeViewFolderPath and knowledgeViewMode also stay separate, because they deliberately outlive the view - switching away from the workspace tab and back must land on the folder being browsed, not the root. New viewForTabPath() replaces the 12-branch tab-path-to-view chain that switchFileTab and closeFileTab each hand-rolled. That fixes a latent bug: closeFileTab had no isCodeTabPath branch, so closing a tab beside the Code tab set selectedPath to the sentinel '__rowboat_code__'. It rendered correctly only because isCodeOpen won the ternary chain, while history recorded a bogus file path. Also lifts the pure helpers App.tsx had accumulated into lib/ (knowledge-tree, view-state, copilot-prompts, and appended to wiki-links and frontmatter). 7,704 -> 6,729 lines. Typecheck clean; renderer suite 134 passing, from 119. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors the Electron renderer’s App.tsx view-selection logic to use a single ViewState discriminated union as the source of truth, eliminating the prior set of mutually-exclusive boolean useState flags and the associated sync/guard complexity. It also extracts a set of pure helper modules from App.tsx into src/lib/ and adds targeted unit tests to pin behavior (notably sentinel tab-path → view mapping and wiki-link rewrite behavior).
Changes:
- Collapse “which view is open” into
useState<ViewState>and derive allis*Openflags and selected payloads from it. - Extract pure helpers into
src/lib/(view-state,knowledge-tree,copilot-prompts, plus wiki-link/frontmatter helpers). - Add Vitest coverage for
viewForTabPathsentinel handling and wiki-link rewrite behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/x/apps/renderer/src/App.tsx | Replaces many view booleans with a single ViewState and updates navigation/guards to derive view flags from it. |
| apps/x/apps/renderer/src/lib/view-state.ts | Introduces shared ViewState union + sentinel tab paths + deep-link parsing as a pure helper module. |
| apps/x/apps/renderer/src/lib/view-state.test.ts | Adds tests that ensure every sentinel tab path maps to the correct ViewState variant. |
| apps/x/apps/renderer/src/lib/wiki-links.ts | Exports a shared wiki-link token regex and extracts rename rewrite helpers from App.tsx. |
| apps/x/apps/renderer/src/lib/wiki-links.test.ts | Adds unit tests for wiki-link rewriting on rename (full path, bare name, prefix/alias/anchor preservation). |
| apps/x/apps/renderer/src/lib/knowledge-tree.ts | Extracts knowledge sidebar tree building/sorting/flattening helpers from App.tsx. |
| apps/x/apps/renderer/src/lib/frontmatter.ts | Adds parseLinkedGoogleDocFrontmatter extraction for linked Google Doc metadata. |
| apps/x/apps/renderer/src/lib/copilot-prompts.ts | Extracts prompt string builders used by UI affordances. |
Comments suppressed due to low confidence (1)
apps/x/apps/renderer/src/lib/view-state.ts:100
- The parseDeepLink JSDoc lists only a subset of supported
typevalues, but the implementation supports additional targets (workspace, knowledge-view, chat-history, home, code, bg-tasks, apps). Keeping this list accurate helps prevent incorrect deep links.
* Shape: rowboat://open?type=<file|chat|graph|task|suggested-topics|meetings|live-notes|email>&...
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+3716
to
+3719
| // Re-opens the remembered view directly rather than replaying navigation, | ||
| // so views that need side-effects on entry (a file tab, a workspace path, | ||
| // a knowledge-view mode) are deliberately not restorable: report the miss | ||
| // and let the caller fall back instead of leaving a blank full-screen chat. |
| if (a.type === 'workspace' && b.type === 'workspace') return (a.path ?? '') === (b.path ?? '') | ||
| if (a.type === 'knowledge-view' && b.type === 'knowledge-view') return (a.folderPath ?? '') === (b.folderPath ?? '') && (a.mode ?? '') === (b.mode ?? '') | ||
| if (a.type === 'email' && b.type === 'email') return (a.threadId ?? '') === (b.threadId ?? '') && (a.searchQuery ?? '') === (b.searchQuery ?? '') | ||
| return true // both graph |
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.
What
App.tsxmodelled "which view is showing" twice:ViewStatediscriminated union — but only used for back/forward history and deep links.useStatebooleans, hand-synced across ~700 setter calls, plus 13 sites that enumerated the whole set in a&&/||chain.There is now one
useState<ViewState>, and everyis*Openflag plusselectedPathandselectedBackgroundTaskis derived from it.Why it was a bug class, not just noise
The render is a ternary chain. Two booleans true silently rendered the first match; all false rendered a blank pane. Neither errored. Deriving the flags makes both states unrepresentable rather than merely unlikely.
Shape of the change
setViewcalls.App.tsx: 7,704 → 6,729 lines.Three decisions worth flagging for review, because each looks like an inconsistency but isn't:
isBrowserOpenstays a separate boolean. It overlays whatever view is showing rather than being one —handleToggleBrowsersets it without clearing anything.isTabbedViewandisTabbedViewExceptCodediffer only by the Code view. Several guards predate Code and still exclude it; folding them together would change behaviour, so both sets are kept and commented.workspaceInitialPath/knowledgeViewFolderPath/knowledgeViewModestay separate state. They deliberately outlive the view — switching away from the workspace tab and back must land on the folder being browsed, not the root. I folded them in first, caught the regression, and reverted with a comment explaining why.Latent bug fixed in passing
New
viewForTabPath()replaces the 12-branch tab-path→view chain thatswitchFileTabandcloseFileTabeach hand-rolled.closeFileTabhad noisCodeTabPathbranch, so closing a tab beside the Code tab setselectedPathto the sentinel'__rowboat_code__'. It rendered correctly only becauseisCodeOpenwon the ternary chain, while history recorded a bogus file path.Also
Lifts the pure helpers
App.tsxhad accumulated intolib/—knowledge-tree,view-state,copilot-prompts, plus additions towiki-linksandfrontmatter. No behaviour change; extracted as-is.Verification
npm run typecheckclean.view-state.test.tsandwiki-links.test.ts).npm run lintunchanged at the 11 pre-existing errors inpackages/coreandpackages/shared— none in renderer, none introduced here.The
viewForTabPathtests specifically pin the failure mode the refactor exists to prevent: a sentinel tab path missing its branch falls through to{ type: 'file' }, which renders a blank middle pane instead of erroring.Not included
Converting the render ternary chain to
switch (view.type). Large JSX diff for zero behaviour gain now that the flags are mutually exclusive by construction — worth doing whenever that JSX needs editing anyway.