Hosted onnoosphere.hyper.mediavia 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