Hosted onsemillabitcoin.comvia theHypermedia Protocol

Problem

The sidebar has no memory. Every time the app restarts, all sections expand, items revert to backend-driven activity sort, and there's no way to hide sections the user doesn't care about. Users with many subscriptions and contacts have a cluttered sidebar they can't organize. There's also no way to reorder sections or items — the layout is fully hardcoded.

Specific pain points:

  • Collapse state resets on reload (stored as React.useState(false))

  • Item ordering is always "by recent activity" from the backend — no alphabetical or manual option

  • Users can't hide sections they don't use (e.g., someone who doesn't use Following)

  • Section order is hardcoded in JSX — no way to prioritize what matters

  • Library and Drafts are pinned in the footer with no visibility control

Solution

A UIPreferences-v001 key in electron-store persists sidebar preferences globally (same across all windows). Follows the existing Bookmarks-v001, Settings-v001 pattern.

What users get:

  • Collapse state persists across restarts

  • Per-section sort mode (activity/alphabetical/manual) toggled via icon in section header

  • Drag-and-drop to reorder items within any section — dragging in any sort mode locks the current order and switches to manual

  • Drag-and-drop to reorder sections directly in the sidebar

  • Visibility toggles in Settings > General > Sidebar

  • Reset to defaults button

  • Drop indicator line (primary color) shows exact insertion point during DnD

How it works with live data:

  • itemOrder stores stable string IDs, reconciled with live API data via mergeWithUserOrder(): new items at the end, removed items silently dropped

  • itemOrder written only on explicit DnD drop, never auto-updated

  • Switching away from manual preserves itemOrder so the user can return

Sort mode transitions:

  • manual -> activity/alphabetical: itemOrder preserved

  • activity/alphabetical -> manual (via icon): restores saved itemOrder, or locks current order if none

  • Drag in any mode: locks current display order + drag as new itemOrder, sets manual

Architecture

Files created

  • frontend/apps/desktop/src/app-ui-preferences.ts — Main process tRPC router (electron-store persistence)

  • frontend/apps/desktop/src/models/ui-preferences.ts — Renderer React Query hooks

  • frontend/apps/desktop/src/utils/merge-user-order.ts — Pure utility for reconciling user order with live data

  • frontend/apps/desktop/src/utils/merge-user-order.test.ts — Unit tests

Files modified

  • frontend/apps/desktop/src/app-api.ts — Registered uiPreferences router

  • frontend/packages/shared/src/models/query-keys.ts — Added UI_PREFERENCES key

  • frontend/apps/desktop/src/components/sidebar.tsx — Persistent collapse, sort icons, visibility, section ordering, DnD with drop indicators

  • frontend/apps/desktop/src/pages/settings.tsx — Sidebar visibility toggles + reset in General Settings

Schema

type SidebarSectionId = 'joined-sites' | 'following' | 'bookmarks' | 'library' | 'drafts'

type SidebarSectionPrefs = {
  collapsed: boolean
  visible: boolean
  sortMode: 'activity' | 'alphabetical' | 'manual'
  itemOrder: string[]
}

type UIPreferencesState = {
  sidebar: {
    sectionOrder: SidebarSectionId[]
    sections: Partial<Record<SidebarSectionId, Partial<SidebarSectionPrefs>>>
  }
}

DnD stack

  • @atlaskit/pragmatic-drag-and-drop (already in repo)

  • @atlaskit/pragmatic-drag-and-drop-hitbox (added) — closest-edge detection + getReorderDestinationIndex

  • Drop indicator: custom DropIndicatorLine component using bg-primary absolute-positioned line

Rabbit Holes

  • Syncing preferences across devices: Local-only electron-store. Cross-device sync needs a backend service and conflict resolution — not now.

  • Per-window sidebar layout: Decided global. Per-window means duplicating into WindowState-v004 and handling divergence.

  • Animating drag-and-drop: pragmatic-drag-and-drop supports it but variable-height sidebar items make it fiddly. Start without, add later.

  • Undo for accidental drag: Cycling sort back to activity/alphabetical effectively undoes it. No separate undo system.

  • Complex sort options (by date joined, by doc count, etc.): Activity + alphabetical covers 95%. More can be added later without schema changes.

  • Keyboard-accessible reordering: pragmatic-drag-and-drop has a11y features but proper wiring is non-trivial. Defer.

No Goes

  • My Site section customization: Always visible, always first — it's identity context.

  • Web app support: Desktop-only (electron-store). Web uses localStorage/IndexedDB. Revisit independently.

  • Sidebar section creation: No custom sections or widgets.

  • Per-section column layouts: Items are always a vertical list — no grid/card views.

  • Drag items between sections: Semantically doesn't make sense (subscription != bookmark). DnD is within-section only.

  • Search/filter within sidebar sections: Separate feature, orthogonal to preferences.

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime