Repository navigation
refactor(app): move the sidebar and drawer layout state into a hook - #1766
Conversation
The drawer, wide-screen close, edge swipe, Escape capture, sidebar collapse/width and file panel width move verbatim into src/lib/useSidebarLayout.ts, called where the drawer state stood. selectionRef stays in App; the hook ceiling is lowered to 163. Plan .omo/plans/app-stores-split.md wave 4.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Summary
Merge Risk: ⚪ Minimal · up to
|
|
Merge pass: move-only PR (wave 4 of the App.tsx split). It merges on its checks without waiting for a CodeRabbit review, as AGENTS.md allows. Diff. Compared as a multiset of lines, the removed and added lines differ only in:
Gate review, by a reviewer on a different model than the author's:
Tests. 45 unit tests and the typecheck pass at this head (merge-pass run). All required checks are green and no review thread is open. |
) Wave 5 of the App.tsx split. The plan is `.omo/plans/app-stores-split.md` in the maintainer's workspace, and plan-reviewer approved it. It follows PR #1766. This wave moves this device's alert and notification state out of `src/App.tsx` into `src/lib/useAlerts.ts`. It also moves what the event stream does for alerts when a pane's status changes or a pane exits. **This is not purely moved code.** The event-stream effect stays in App, and three pieces of its handler become calls to hook callbacks, so this PR waits for a review. Every line the pieces held moves byte for byte, apart from indentation. ## What moves Six blocks move verbatim, in source order, into `useAlerts`: - **(A)** the alert preferences: `alerts`, `alertsRef`, `alertsOn` and `alertsOnRef`. - **(B)** `notifications`, `pushOn` and `pushOnRef`. - **(C)** the status refs: `statusRef`, `turnStartRef`, `lastTurnRef`, `alertInAppRef` and `alertSoundRef`. `followFocusRef` stays in App. - **(D)** `dropIn`, the alert-sound player effect, `chime`, and the sound-unlock effect. - **(E)** `enableNotifications`, `disableNotifications`, and the push re-registration effect. - **(F)** the bell, `bellVisible` and `alertsOffDot`. In the event-stream effect, three pieces become calls at exactly the points the lines stood: - **Roster:** `onRoster(payload.machines)` is the roster's `seedStatuses` line. The only token that changes is `payload.machines`, which becomes the parameter `machines`. - **Pane status:** `onPaneStatus(machine, message, takeNotificationTargetRef)` covers the status bookkeeping, the in-app drop and chime, and the system notification. The `setMachines(applyPaneStatus…)` update stays in App, right after the call. - **Pane exit:** `onPaneExited(machine, message, takeNotificationTargetRef)` covers the ended-turn drop and chime, then the ended notification. The `herdr-usage-changed` dispatch stays in App, before the call. `takeNotificationTargetRef` is declared later in App, so the effect passes the ref object at event time. The moved lines still read `takeNotificationTargetRef.current` at the same moment as before. ## Why behaviour is unchanged **Hook call position:** the hook is called where `notifications` was declared, before App's `followFocusRef`. The alert values were not read anywhere between their old declaration and that point. Everything the hook takes is declared before it: `settings`, `updateSettings`, `t`, `locked`, `selectionRef`, `drawerOpenRef`, `splitRef`, `splitShownRef` and `machinesRef`. **No resubscription:** `dropIn`, `chime`, `onRoster`, `onPaneStatus` and `onPaneExited` are all `useCallback(..., [])`. The event-stream effect's dependency list `[locked, scheduleRefetch, dropIn, chime, reorderCoordinator]` is unchanged, so the stream does not resubscribe. `enableNotifications` and `disableNotifications` keep `[updateSettings]`. **Effect order:** three effects now run earlier in a commit than before: - **The alert-sound player effect and the sound-unlock effect** now run before App's load-and-health effect. They only create a player and listen for taps, independent of loading. - **The push re-registration effect** now runs before the load-and-health effect and the event-stream effect. - On first load it returns at once, because `locked` is still `null`. - When the lock opens, it and the event-stream effect re-run in the same commit, in the opposite order. Both only start asynchronous network calls (a push subscription; an EventSource) and share no state. **The bell:** it is computed during render. It reads only values the hook owns, plus `settings` and `t`. **Hook ceiling:** `APP_HOOK_CEILING` drops from 163 to 144. That is the 19 hook calls that moved: 3 in A, 3 in B, 5 in C, 5 in D and 3 in E. ## Checks - **`--color-moved`:** `git diff --color-moved=plain --color-moved-ws=allow-indentation-change origin/main...HEAD` shows 361 lines as moved. The other 57 lines are: - App's narrowed imports, the new import and the call; - the three event-stream calls and the `onRoster` line; - the new file's imports, params interface, message types, signature, the three callback headers and closers, the doc comment and the return; - the ceiling. - **Moved lines are exact:** blocks A-F appear byte for byte in the hook (lines 225-232, 496-500, 509-518, 604-647, 744-787 and 1317-1343 of `App.tsx` on `53b85127e`). The event-stream lines 677-689, 704-709 and 711-714 appear apart from indentation. - **Typecheck:** `bun run typecheck` passes. - **Unit tests:** **129 pass, 0 fail across 9 files**, from: `HERDR_TEST_MODE=unit bun test src/app-hooks.test.ts src/conventions.test.ts src/lib/notifications.test.ts src/lib/droplet.test.ts src/lib/alertTurns.test.ts src/lib/alertSound.test.ts src/lib/push.test.ts src/lib/notificationTarget.test.ts src/lib/i18n.test.ts` - **Ceiling test:** adding `const zz = useState(false);` to App.tsx makes `src/app-hooks.test.ts` fail. - `bun run check fast`: passes after the rebase (unit 6253 pass, 0 fail, 13 skip in 443 files, 8 at a time, 57.5s) - **`bun run check integration browser`:** the browser lane exits 0 (every browser script passed, 1816 s), including the alert scripts: the Alerts item off/on, blocked-permission switch, Settings push test, notification startup, a tapped pane alert opening chat/terminal/workspace, in-app alerts (open pane spared, drop-in, flick, close, timeout, off stays off, split half spared) and the alert sound (no sound before a tap, Settings toggle with preview, two tabs chime once). It ran on this commit before its rebase onto the latest main, which changed only PaneTerminal files. The integration lane's only failure is `server/api.contract.test.ts` "creates, edits, searches, inspects, and closes an owned workspace" (line 1540), which fails the same way on unchanged `origin/main` on this PC; CI runs that lane in its own environment. ## Outside PRs that touch the same files No other maintainer PR touches these files. One outside contributor's PR touches `src/App.tsx`: #1427. It already conflicts with main there, and in eight other files, so it needs a rebase either way. No changelog fragment: users see no change.
Wave 4 of the App.tsx split. The plan is
.omo/plans/app-stores-split.mdin the maintainer's workspace. It follows PR #1762.This wave moves the drawer, sidebar and file-panel layout state out of
src/App.tsxintosrc/lib/useSidebarLayout.ts. The hook runs inside App's render, so updates and effect order are unchanged.What changes
Three blocks move verbatim, in their order, into
useSidebarLayout({ initialLink }):drawerOpenRef,wideScreen, and the effect that closes the drawer on a wide window.sidebarCollapsed,sidebarWidth,filePanelAvailableandfilePanelWidth.The hook returns those values, their setters, and the same
drawerOpenRefobject.What stays in
App.tsx:selectionRefand its layout effect, which sat between the blocks but belong to selection. They now follow the call directly.Listener order: the
lastKeyAtkeydown listener still comes after the call, so the Escape capture listener still registers first.Imports:
storedSidebarWidth,FILE_PANEL_DOCK_QUERY,storedFilePanelWidthandwatchDrawerSwipeleave App's imports.Hook ceiling:
APP_HOOK_CEILINGdrops from 171 to 163, after moving fouruseState, oneuseRefand threeuseEffectcalls.Checks
--color-moved:git diff --color-moved=plain --color-moved-ws=allow-indentation-change origin/main...HEADshows 56 lines as moved. The other 22 lines are:App.tsxone44fc9f8f) appears byte for byte in the hook.bun run typecheckpasses.HERDR_TEST_MODE=unit bun test src/app-hooks.test.ts src/lib/edgeSwipe.test.ts src/lib/sidebarWidth.test.ts src/lib/filePanelWidth.test.ts src/conventions.test.ts: 45 pass, 0 fail.const zz = useState(false);to App.tsx makes it fail.bun run check fast: passes after the rebase (unit 6253 pass, 0 fail, 13 skip in 443 files, 8 at a time, 57.6s)bun run check integration browser: the browser lane exits 0 (every browser script passed, 2036 s), run on this commit before its rebase onto the latest main (which changed only PaneTerminal and server files). The integration lane had two failures, none in code this PR touches:server/api.contract.test.ts"creates, edits, searches, inspects, and closes an owned workspace" (line 1540, fails the same way on unchangedorigin/mainon this PC) andserver/gjc.contract.test.ts"binds same-cwd panes independently by native PID…" (pane.process_infodid not yet list the isolated GJC process while other lanes ran on the same PC); rerun alone on this branch,server/gjc.contract.test.tspasses (10 pass, 0 fail). CI runs the integration lane in its own environment.Outside PRs that touch the same files
No other maintainer PR touches these files. One outside contributor's PR touches
src/App.tsx: #1427. It already conflicts with main inApp.tsx, and needs a rebase either way.Move-only: moved lines plus imports, the hook signature and return, one call and the lowered ceiling. No changelog fragment: users see no change.