Overhauls the message timeline sidebar with segment-level selection,
token-aware xray histogram bars, and messageId-based grouping — replacing
the previous message-level selection and positional adjacency logic.
## Selection System (SELECTION-SYSTEM)
- Dual-level selection: `selectedTimelineIds` (segment IDs) as the
source of truth, bridged to `selectedForDeletion` (message IDs) via
a reactive `createEffect`.
- CTRL+Click: toggles individual segments. Clicking an assistant parent
with unexpanded tools expands the group and selects all members.
Re-clicking collapses and deselects.
- SHIFT+Click: range selection. Direction follows anchor state — if the
anchor is selected the range is additive; if not, subtractive.
- Escape: clears all selection via a global keydown listener.
- Long-press (500ms, 10px jitter tolerance): mobile/touch selection
via pointer events with context-menu suppression.
- Scroll anchor preservation: captures badge offsetTop before toggling
visibility, restores scrollTop after layout shift.
## Token Count Fix (TOKEN-COUNT-FIX)
- New `getPartCharCount()` estimates characters for any `ClientPart`.
Handles text, tool state (input/output/metadata), and content arrays.
- **Skips `filediff` metadata key** — this key contains full before/after
file content that inflated character counts by 10-100x.
- `totalChars` field added to `TimelineSegment` and `PendingSegment`,
accumulated during `buildTimelineSegments()`.
## Scroll Performance (SCROLL-PERF)
- Two-tier positioning replaces per-badge `getBoundingClientRect` on
every scroll event:
1. `computeBadgeLayout()` — expensive pass, runs once on activation,
resize, or expansion. Stores `layoutTop` relative to scroll content.
2. `handleScrollRaf()` — RAF-throttled, reads 1 container rect per
frame. Derives all badge screen positions arithmetically.
- `clipBounds` subtracts delete toolbar height + 16px gap when toolbar
is visible, preventing xray bars from overlapping the toolbar.
## Group Logic (GROUP-LOGIC)
- `getAdjacentGroup()`: changed from backward positional walk to
`segments.filter(s => s.messageId === clicked.messageId)`. Fixes
cross-message group overlap when consecutive tool segments belong to
different assistant messages.
- `groupRole()`: checks for sibling tools via `messageId`.
- `isGroupStart()`: checks previous segment's `messageId`.
- Only assistant badges trigger group selection; tool and user badges
are always standalone.
## Active Highlight (ACTIVE-HIGHLIGHT)
- Renamed `activeMessageId` → `activeSegmentId` (signal, prop, and
comparison). Clicking a badge now highlights only that specific badge,
not all badges sharing the same messageId.
- Intersection observer resolves messageId → first segment's id.
- Auto-scroll effect uses segment id directly (no `.find()` lookup).
## XRay Histogram Bars (XRAY-BARS)
- Portal-based overlay with two bars per segment:
- Relative bar: width = tokens/maxTokens, green-to-red gradient.
- Absolute bar: width = tokens/10000 (capped), grey, with red glow
overflow indicator when tokens exceed ABSOLUTE_TOKEN_CAP (10K).
- Token labels as pill-shaped badges (white bg, dark border, 12px font,
1.5rem height matching badge height) at the left tip of each bar.
- `liveSegmentChars` memo fetches fresh char counts from the message
store to handle stale tool output that arrived after segment creation.
- `aggregateTokensByMessageId` memo: O(n) pre-computation replacing the
previous O(n²) per-segment iteration inside `<For>`.
- `clip-path: inset(...)` clips bars at layout edges.
## Delete Toolbar Token Display (TOKEN-TOTAL-IN-TOOLBAR)
- Removed `outputTokensByMessageId` (backend `entry.outputTokens` only
counted assistant output, missing tool result content entirely).
- `selectedTokenTotal` now sums `seg.totalChars` across all segments
for each selected messageId, divides by 4. Consistent with xray bars.
- Three color-coded pills: Before (muted, current context), Selection
(red, tokens being removed), After (green, remaining after deletion).
Eliminates mental arithmetic for users targeting a context token count.
## Delete Hover Fix
- Removed `selected.has(segment.messageId)` → `return true` from
`isDeleteHovered()`. The red delete overlay now only activates from
actual hover interactions (kind === "message" or "deleteUpTo"), not
from the selection state. This prevents the red overlay from masking
the blue segment-level selection highlight.
## CSS Changes
- message-selection.css: Restyled toolbar with accent-primary scheme,
three-pill token group, button variants (--delete, --cancel), hint.
- message-timeline.css: Selection styling (!important overrides), group
indicators (left border), xray overlay (fixed fullscreen, z-index 40),
rib/bar/label styles, container layout, stacking context isolation.
## Files Changed
- packages/ui/src/components/message-section.tsx (+345/-197)
- packages/ui/src/components/message-timeline.tsx (+671/-199)
- packages/ui/src/lib/i18n/messages/en/messaging.ts (+1/-2)
- packages/ui/src/styles/messaging/message-selection.css (+107/-34)
- packages/ui/src/styles/messaging/message-timeline.css (+146/-0)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CodeNomad
A fast, multi-instance workspace for running OpenCode sessions.
CodeNomad is built for people who live inside OpenCode for hours on end and need a cockpit, not a kiosk. It delivers a premium, low-latency workspace that favors speed, clarity, and direct control.
Manage multiple OpenCode sessions side-by-side.
📸 More Screenshots
Global command palette for keyboard-first control.
Getting Started
Choose the way that fits your workflow:
🖥️ Desktop App (Recommended)
The best experience. A native application (Electron-based) with global shortcuts, deeper system integration, and a dedicated window.
- Download: Grab the latest installer for macOS, Windows, or Linux from the Releases Page.
- Run: Install and launch like any other app.
🦀 Tauri App (Experimental)
We are also working on a lightweight, high-performance version built with Tauri. It is currently in active development.
- Download: Experimental builds are available on the Releases Page.
- Source: Check out
packages/tauri-appif you're interested in contributing.
💻 CodeNomad Server
Run CodeNomad as a local server and access it via your web browser. Perfect for remote development (SSH/VPN) or running as a service.
npx @neuralnomads/codenomad --launch
Full server/CLI documentation (flags + env vars, TLS, auth, remote access):
To see all available options:
npx @neuralnomads/codenomad --help
🧪 Dev Releases
Bleeding-edge builds are published as GitHub pre-releases and are generated automatically from the dev branch.
npx @neuralnomads/codenomad-dev --launch
Highlights
- Multi-Instance: Juggle several OpenCode sessions side-by-side with tabs.
- Long-Session Native: Scroll through massive transcripts without hitches.
- Command Palette: A single global palette to jump tabs, launch tools, and control everything.
- Deep Task Awareness: Monitor background tasks and child sessions without losing flow.
Requirements
- OpenCode CLI: Must be installed and available in your
PATH. - Node.js 18+: Required if running the CLI server or building from source.
Troubleshooting
macOS says the app is damaged
If macOS reports that "CodeNomad.app is damaged and can't be opened," Gatekeeper flagged the download because the app is not yet notarized. You can clear the quarantine flag after moving CodeNomad into /Applications:
xattr -l /Applications/CodeNomad.app
xattr -dr com.apple.quarantine /Applications/CodeNomad.app
After removing the quarantine attribute, launch the app normally. On Intel Macs you may also need to approve CodeNomad from System Settings → Privacy & Security the first time you run it.
Linux (Wayland + NVIDIA): Tauri AppImage closes immediately
On some Wayland compositor + NVIDIA driver setups, WebKitGTK can fail to initialize its DMA-BUF/GBM path and the Tauri build may exit right away.
Try running with one of these environment variables:
# Most reliable workaround (can reduce rendering performance)
WEBKIT_DISABLE_DMABUF_RENDERER=1 codenomad
# Alternative for some Wayland setups
__NV_DISABLE_EXPLICIT_SYNC=1 codenomad
If you're running the Tauri AppImage and want the workaround applied every time, create a tiny wrapper script on your PATH:
#!/bin/bash
export WEBKIT_DISABLE_DMABUF_RENDERER=1
exec ~/.local/share/bauh/appimage/installed/codenomad/CodeNomad-Tauri-0.4.0-linux-x64.AppImage "$@"
Upstream tracking: https://github.com/tauri-apps/tauri/issues/10702
Architecture & Development
CodeNomad is a monorepo split into specialized packages. If you want to contribute or build from source, check out the individual package documentation:
| Package | Description |
|---|---|
| packages/electron-app | The native desktop application shell. Wraps the UI and Server. |
| packages/server | The core logic and CLI. Manages workspaces, proxies OpenCode, and serves the API. |
| packages/ui | The SolidJS-based frontend. Fast, reactive, and beautiful. |
Quick Build
To build the Desktop App from source:
- Clone the repo.
- Run
npm install(requires pnpm or npm 7+ for workspaces). - Run
npm run build --workspace @neuralnomads/codenomad-electron-app.

