Skip to content

feat(git-diff): persist diff view mode and line wrap across reloads - #3271

Open
danielbachhuber wants to merge 1 commit into
get-bb:mainfrom
danielbachhuber:bb/fork-repository-and-configure-origin-thr_3qsjm6wmdy
Open

feat(git-diff): persist diff view mode and line wrap across reloads#3271
danielbachhuber wants to merge 1 commit into
get-bb:mainfrom
danielbachhuber:bb/fork-repository-and-configure-origin-thr_3qsjm6wmdy

Conversation

@danielbachhuber

@danielbachhuber danielbachhuber commented Sep 8, 2026

Copy link
Copy Markdown

Human comments

From https://discord.com/channels/1522478522332090528/1545179385194356856/1545395969603014657

What was wrong

Both git-diff toolbar preferences were component state, so they reset on remount and reload. Display mode also expired explicit choices during panel resizing, breakpoint changes, and panel closes; the 760px rule then overrode the choice.

What changed

Added device-local localStorage preferences for git-diff display mode (bb.thread.gitDiff.displayMode) and line overflow mode (bb.thread.gitDiff.lineOverflowMode) in apps/app/src/lib/git-diff-view-preferences.ts. apps/app/src/components/secondary-panel/git-diff/useResponsiveGitDiffPanelDisplay.ts now uses the stored display preference and exports a pure resolver; apps/app/src/components/secondary-panel/ThreadSecondaryPanel.tsx persists the wrap toggle and passes the resize handler through directly.

A stored display-mode preference overrides the 760px responsive rule at every panel width. The rule supplies the default only when no preference is stored. On a compact viewport, display mode is session-only, defaults to unified, and is never persisted, so mobile never overwrites the desktop preference.

Decisions

  • Persist these preferences with Jotai atomWithStorage in localStorage, not AppSettings or PUT /settings/general: they are device-local view preferences and do not need server or CLI access.
  • Apply persistent display-mode preferences only outside a compact viewport. Compact mode uses session state so a desktop split preference does not carry onto a phone-width drawer and a mobile choice does not overwrite the desktop preference.
  • Use the existing useIsCompactViewport() signal (max-width: 767px) rather than a new breakpoint or panel-width proxy; tests use CompactViewportOverrideProvider.
  • Keep the compact toggle functional: unified is its default, not a restriction, and an explicit compact split choice is honored for the session.
  • Honor an explicit display choice at every panel width. The 760px breakpoint remains the fallback only for an unset preference, accepting that stored split can be cramped below that width.
  • Remove the old expiry behavior instead of combining it with persistence, because resizing across 760px would otherwise discard the stored choice.
  • Store display mode as nullable so an unset value remains distinguishable from a user choosing unified, preserving the responsive default for users who have not chosen.
  • Delete handleSecondaryPanelResizeStart, whose only purpose and caller supported expiry-on-resize.
  • Reuse createNullableLocalStorageEnumStorage and createLocalStorageEnumStorage from lib/browser-storage.ts; the preferences are string unions, so createBooleanPreferenceAtom does not apply.
  • Use the existing bb.<area>.<name> storage-key convention and read storage on first render with { getOnInit: true }, matching the app's other atomWithStorage usage and avoiding a default-state flash.
  • Extract resolveGitDiffDisplayMode({ isCompactViewport, compactDisplayMode, displayModePreference, isWideEnoughForSplit }) as a pure exported function so its combinations can be tested without rendering.
Implementation and scope

useResponsiveGitDiffPanelDisplay.ts replaces its in-memory display state and tracking refs with the compact-viewport signal, stored preference, compact session state, and an isWideEnoughForSplit: boolean | null state. It removes the resize-start handler, explicit-choice refs, breakpoint-crossing logic, and panel-close reset effect, all of which existed to expire the choice. The split breakpoint remains GIT_DIFF_SPLIT_VIEW_MIN_WIDTH_PX (760px).

useResponsiveGitDiffPanelDisplay.test.tsx adds 13 tests covering responsive fallback, preference precedence at every width, remount restoration, invalid storage values, compact defaults and toggles, compact non-persistence, preservation of an existing stored preference, and resolver combinations.

The collapse-all chevron is intentionally not persisted. Its default is derived per diff from file count and delete status, rather than from a user choice, so persisting it would conflict with that per-diff behavior. FilePreview.tsx and markdown-preview.tsx retain their independent unpersisted wrap toggles; sharing a preference is a separate product decision. There is also no settings-panel UI for these preferences.

Outside compact mode, there is no UI to return display mode to an unset responsive state: choosing unified on a wide panel remains a preference until storage is cleared. The compact session choice survives viewport changes within the same hook instance, which is intended but untested. Storage's subscribe implementation provides cross-tab sync, which the new tests do not exercise. Because localStorage is per origin, preferences do not carry between the packaged app and a checkout's dev app on a different port.

How you verified

  • pnpm exec vitest run --config vitest.config.ts useResponsiveGitDiffPanelDisplay (13 tests passed)
  • pnpm exec vitest run --config vitest.config.ts secondary-panel (37 files and 352 tests passed)
  • pnpm exec tsc --noEmit (exited 0 with no output)
  • pnpm exec oxlint src (exited 0; 184 warnings are the pre-existing baseline, and neither changed file appears in the output)

No manual reload check was performed in a running app; the automated remount-and-restore tests cover the persistence path, and reviewers should toggle both controls and reload within a single app instance.

AGENT GENERATED

Store the git diff toolbar's display mode and line overflow mode in
localStorage-backed Jotai atoms so both survive a remount and reload.

A stored display-mode preference now overrides the 760px responsive rule
at every panel width, which replaces the expiry bookkeeping that
discarded an explicit choice on resize, breakpoint crossing, and panel
close. On a compact viewport the display mode is session state that
defaults to unified and is never persisted, so mobile keeps an
opinionated default and never overwrites the desktop preference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant