Skip to content
Sidebar & Navigation

Sidebar & Navigation

The sidebar provides the primary navigation structure for the Loop desktop app. It lists channels and threads, supports drag-and-drop reordering, batch selection, search filtering, and houses the app’s footer actions.

Related docs: Desktop App | Layouts | Settings | Chat


Layout

The sidebar is a vertical column on the left side of the app with the following sections, top to bottom:

  1. Drag region (38px) – macOS window dragging area with a collapse button at the top-right
  2. Header bar – “CHANNELS” label with Select and “+ new” buttons
  3. Search box – filter channels and threads
  4. New channel input – inline text field (shown when creating)
  5. DM channel – pinned at top
  6. Project channels – sortable list with collapsible threads
  7. Spacer – pushes footer to bottom
  8. Footer – update button, settings, README
  9. Resize handle – right edge for width adjustment

Dimensions

PropertyValue
Minimum width180px
Maximum width25% of window width
Default width280px

The width is adjusted via a 4px-wide resize handle on the right edge:

  • Cursor changes to col-resize on hover
  • Handle becomes visible (colors.textDim) on hover and during drag
  • A 1px border-right (colors.border) is always visible
  • user-select: none is applied during drag to prevent text selection

When collapsed (sidebarOpen === false), the entire sidebar is hidden (returns null). An expand button appears in the workspace layout’s top drag region to restore the sidebar.


Channel Ordering

Drag-and-Drop

Top-level channels (not threads, not DM) support drag-and-drop reordering:

  1. Each ChannelItem has draggable set on its container.
  2. On drag start, the channel ID is stored in draggedIdRef.
  3. On drag over, the target channel shows a green top border (2px solid colors.active).
  4. On drop, the channel order array is updated: the source is removed from its position and inserted at the target position.

Persistence

Channel order is stored in localStorage under the key loop-channel-order as a JSON array of channel IDs. Channels not in the stored order sort to the end.


Search

The search box sits below the header and provides real-time case-insensitive filtering.

Behavior

  • Filters top-level channels by name match
  • Shows a parent channel if any of its threads match (even if the parent name does not match)
  • When the parent matches, all its threads are shown
  • When only threads match, only those matching threads are shown under the parent
  • Press Escape to clear the search query

UI

  • Search icon (magnifying glass) positioned inside the input field (left-aligned)
  • Input: full width, colors.bg background, 12px font, 4px border-radius
  • Placeholder: “Search…”

Selection Mode

Clicking “Select” enters batch selection mode.

Behavior in Select Mode

  • Each channel and thread shows a checkbox instead of the collapse chevron
  • The DM channel is excluded from selection (pinned, not selectable)
  • Checked items have a green border and background with a checkmark icon
  • Header shows:
    • “Delete (N)” button – deletes all selected items (red on hover)
    • “Cancel” button – exits selection mode and clears selections

Batch Delete

Channels and threads are deleted individually in sequence. If the currently selected channel is among the deleted, the selection is cleared.


Create Channel

Clicking “+ new” opens a dropdown menu with two options:

OptionAction
New projectShows an inline text input in the sidebar. Enter submits, Escape/blur cancels.
Open directory…Opens a native directory picker dialog (via showOpenDirectoryDialog IPC), then runs onboard:local and creates/selects the channel. Only available in Electron.

The dropdown is a positioned overlay that closes on outside click (left mousedown).


DM Channel

The DM channel is always pinned at the top of the channel list:

  • Identified by name === "dm" and no parent_id
  • Created automatically on first load if it does not exist (ensureChannel)
  • Auto-selected on first load if no channel is selected and no hash is present
  • Cannot be deleted (excluded from context menu delete option)
  • Cannot be selected in batch selection mode

Channel Item

Each channel item (ChannelItem component) displays:

Visual Elements

ElementDescription
Collapse chevronShown when channel has threads. Rotates 90 degrees when collapsed. Click to toggle.
Hash symbol (#)Channel prefix, dimmed text
Channel nameTruncated with ellipsis. Shows dir_path.split("/").pop() if no name set.
Status indicatorGreen dot (6px circle, colors.active) when container_running or agent_running is true
Config button (gear icon)Shown on hover for channels with dir_path. Opens project settings.
“+ thread” buttonShown on hover. Toggles the new thread input.

Interaction

ActionBehavior
Click channel nameSelect the channel
Click collapse chevronToggle thread visibility
Double-click channel rowCollapse/expand threads
HoverShows config and thread buttons; applies colors.hoverBg background
Right-clickOpens context menu
DragInitiates channel reorder

Context Menu

Right-clicking a channel opens a context menu with:

ItemAction
Copy LinkCopies loop://channel/<id> to clipboard
Copy Channel IDCopies the raw channel ID
Delete ChannelDeletes the channel (not shown for DM)

The “Delete Channel” item has danger: true styling (red text) and a separator above it.


Thread Item

Threads are listed below their parent channel, indented with a tree connector line. Threads can contain sub-threads (e.g. scheduled task threads), forming a 3-level hierarchy.

Visual Elements

ElementDescription
Tree connectorSVG with vertical line and horizontal branch at 50% height. Last thread’s vertical line stops at 50%. Positioned at 16px left offset. Z-index 1 to stay above hover backgrounds.
Worktree iconGit branch SVG icon (colors.active) for worktree threads.
Task thread iconClock SVG icon (colors.textDim) for scheduled task threads (detected by task # name prefix).
Ephemeral iconUndo-arrow SVG icon (60% opacity) for ephemeral task threads (detected by [ephemeral] prefix).
Thread nameTruncated with ellipsis. Task thread names have emoji prefixes (🧵, ) stripped for display. Falls back to thread ID.
Status indicatorGreen dot when container_running or agent_running, positioned at the right edge.
CheckboxShown in selection mode instead of normal layout
Sub-threadsRendered recursively below the thread with the same connector style.

Indentation

Threads are indented with padding-left: 30px (accounting for the tree connector). The connector line uses colors.textDisabled (#555555) at 1.5px stroke width.

Interaction

ActionBehavior
ClickSelect the thread
Hovercolors.hoverBg background
Right-clickOpens the same context menu as channels

Create Thread

Clicking “+ thread” on a channel reveals an inline input (NewThreadInput) below the channel. Behavior:

  • Enter submits the thread name
  • Escape or blur cancels
  • The input auto-focuses on mount

Delete Thread

Threads can be deleted via:

  • Context menu “Delete Channel” (same operation for threads)
  • Batch selection mode

Footer

The footer sits at the bottom of the sidebar, separated by a top border.

Update Button

Only shown when updateStatus?.available is true. The button text and behavior depend on the update state:

StateTextColorAction
Available“Update available vX.Y.Z”colors.active (green)Download update
Downloading“Downloading…”colors.textDimDisabled (no action)
Downloaded“Restart to update”colors.active (green)Install and restart
Error“Update failed – click to retry”colors.activeRetry download

The button includes a download icon (arrow into tray).

Kanban Button

  • Board icon + “Kanban” text
  • Click opens the Kanban panel as an overlay
  • colors.textDim text, brightens on hover

Settings Button

  • Gear icon + “Settings” text
  • Click opens the Settings panel
  • Keyboard shortcut: Cmd+, (handled at the app level)
  • colors.textDim text, brightens on hover

README Button

  • Book icon + “README” text
  • Click opens the README markdown panel
  • colors.textDim text, brightens on hover

Window Title

The app title bar updates based on the selected channel/thread (handled in App.tsx):

SelectionTitle
NoneLoop
Channel<channel-name> - Loop
Thread<parent-name> > <thread-name> - Loop