## What changes for the user The timeline now matches the transcript block for block, and a click lands exactly where it should. - **One marker per visible block, in transcript order:** each reply, each thinking run, each tool (or collapsed tool group), each compaction and each system context. Markers follow Message content: a block that is hidden in the transcript is hidden in the timeline too. - **The clicked marker stays active** until you scroll, and its own block, not just the start of its message, is placed at the reading position. This also works for history that is not loaded yet. - **While scrolling,** the active marker is the first block below the reading line. ## Reproduced in numbers before fixing We replayed 45 of the user's real sessions (65,241 messages, exported read-only) into the real `SessionView`, with the outline, windows and previews served by the real server readers. Each session was measured at 1280×800 with two presets, using 16 real pointer clicks and 8 wheel scrolls per run. Baseline and fix used the same seeds. | Metric | Before, Complet | Before, Balanced | After, both presets | |---|---|---|---| | Sessions out of sync | 43/45 | 42/45 | 0/90 runs | | Markers / expected | 68,949 / 130,818 | 68,949 / 98,759 | 130,818 / 130,818 and 98,759 / 98,759 | | Click: clicked marker active | 53.6% (46.0% the one above) | 57.2% | 1,440 / 1,440 | | Click: block at reading position | 41.5% (median off by 35 px) | 82.2% | 1,440 / 1,440 (median 0.27 px) | | Scroll: reading-line block active | 93.1% | 92.2% | 505 / 505 moving wheels | What was wrong with the markers in the Complet runs: - 29,782 tool markers were collapsed into one marker per step. - 31,777 thinking blocks and 282 system blocks had no marker. - There were 10,232 order inversions. ## Causes 1. Once an outline existed, the outline projection replaced the markers of loaded messages too (`message-section.tsx`). It collapsed a step's tools into one marker placed before its reply, and dropped system blocks. 2. Resident segments merged thinking into replies, dropped thinking-only steps and skipped system parts. 3. After a click or a scroll, the active marker was the message's first marker. The message itself came from a scroll offset that rounds a row starting just below the top to the previous message. 4. Clicks navigated to the message, not to the block. 5. Jumps into unloaded history had no row to hold while the target was not yet rendered. ## How - **Server (outline):** each assistant step can now carry an optional `layout` field. It lists the step's visible parts in order: thinking runs, reply runs and each tool, at most 64 entries. It is computed in the same bounded SQL. Older plugin copies don't send it, and the client then falls back to the counts: thinking, then text, then tools. - **Markers:** `buildTimelineSegments` emits one marker per block, keyed `messageId:type:ordinal`. Loaded messages keep their exact markers. Unloaded steps are placed at their native position from `layout`. - **Click and scroll:** the list's `scrollToKey(key, selector)` holds the selected block on every frame, survives row remounts, and follows Virtua's estimate until the row renders. `timeline-transcript-blocks.ts` maps markers to blocks and finds the block at the reading line. - **Saved indexes:** outline indexes saved without `layout` are revalidated once. - **Docs and help:** updated in `dev-docs/SESSION_HISTORY_NAVIGATION.md` and in the help pages for all 10 locales. ## Behaviour to be aware of - **The timeline has more markers:** about +90% with Complet and +43% with Balanced on this sample, because every tool, thinking and system block now has its own marker. - **Tool icons for unloaded steps:** a step not loaded yet shows its first tool's icon on all of its tool markers, until its window loads. - **Plugin requirement:** the pruning plugin must ship with the new `layout` field. ## Validation - **New regression test:** `tests/browser/timeline-sync.test.ts` passes 4/4 and fails 4/4 on unmodified dev. Fixtures are synthetic. - **Measurement harness:** the opt-in qualification `npm run qualify:browser:timeline-sync`. - **Browser suites:** | Suite | Result | |---|---| | history-navigation | 65 | | message-scroll | 128 | | session-rendering | 47 | | render-cost | 17 | | full-source-copy | 12 | | search-highlight | 11 | | system-messages | 10 | | timeline-tool-filters | pass | | streaming-outline (no full reprojection over 64 token deltas) | pass | | session-search | pass | | transcript-presets | pass | - **Other checks:** UI unit tests 75/75, server session-pruning 70/70, help 9/9 and the UI typecheck pass. - **After rebasing on dev:** the typecheck passes, along with timeline-sync 4/4, timeline-tool-filters, full-source-copy 12/12, usage-metrics 5/5 and streaming-outline. Oversized files touched: `message-section.tsx` (~1,637 lines), `virtual-follow-list.tsx` (~1,368), `message-timeline.tsx` (~508), `message-timeline.css` (204). |
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| tests/browser | ||
| .gitignore | ||
| package.json | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.js | ||
| tsconfig.json | ||
| vite.config.ts | ||
CodeNomad UI
This package contains the frontend user interface for CodeNomad, built with SolidJS and Tailwind CSS.
Overview
The UI is designed to be a high-performance, low-latency cockpit for managing OpenCode sessions. It connects to the CodeNomad server (either running locally via CLI or embedded in the Electron app).
Features
- SolidJS: Fine-grained reactivity for high performance.
- Tailwind CSS: Utility-first styling for rapid development.
- Vite: Fast build tool and dev server.
Development
To run the UI in standalone mode (connected to a running server):
npm run dev
This starts the Vite dev server at http://localhost:3000.
Building
To build the production assets:
npm run build
The output will be generated in the dist directory, which is then consumed by the Server or Electron app.
Debug Logging
The UI routes logging through a lightweight wrapper around debug. Its namespaces can be toggled at runtime:
sse– Server-sent event transport and handlersapi– HTTP/API calls and workspace lifecyclesession– Session/model state, prompt handling, tool callsactions– User-driven interactions in UI componentsforeground-refresh– Visible-state reconciliationscroll– Transcript position writes, follow-mode transitions and unclaimed (unowned) scrolls; seedev-docs/SESSION_HISTORY_NAVIGATION.md
You can enable or disable namespaces from DevTools (in dev or production builds) via the global window.codenomadLogger helpers:
window.codenomadLogger?.listLoggerNamespaces() // => [{ name: "sse", enabled: false }, ...]
window.codenomadLogger?.enableLogger("sse") // turn on SSE logs
window.codenomadLogger?.disableLogger("sse") // turn them off again
window.codenomadLogger?.enableAllLoggers() // optional helper
Enabled namespaces are persisted in localStorage under opencode:logger:namespaces, so your preference survives reloads.