## Summary
- virtualize MessageTimeline so large session histories stop rendering
the full timeline sidebar at once.
- keep the existing full render path in selection mode so xray/selection
behavior stays intact.
- route active-segment scrolling through the virtualizer so timeline
navigation still follows the selected message.
## Benefit
- prompt field was very laggy in cession with big history and timeline
had many bugs, this is fixed.
- the session with big history now load as fast as a new session .
## Summary
- add SideCar support across the server and UI, including proxied tabs,
picker/settings flows, and websocket-aware proxying
- unify top-level tab handling so workspace instances and SideCars share
the same tab model and navigation flows
- limit SideCars to port-based services only, removing server-managed
process control from the final API and UI
---------
Co-authored-by: Shantur <shantur@Mac.home>
Co-authored-by: Shantur <shantur@Shanturs-MacBook-Pro-M5.local>
# PR Title
Implement shared compact split and unified tool-call diff layout
---
Fixes#268
# PR Description
## Summary
This PR makes tool-call diffs more compact in both `Unified` and `Split`
views by reducing wasted horizontal space in line-number gutters and
content indentation.
## What changed
- introduced a shared compact-diff framework for tool-call diffs
- kept mobile-specific policy limited to:
- forcing unified mode below the breakpoint
- enabling wrap only in mobile unified mode
- added mode-specific compact applicators in the diff viewer:
- unified applicator
- split applicator
- reduced gutter width waste by measuring rendered line-number text and
tightening column width around it
- removed unnecessary right-side content padding
- aligned `+` / `-` markers closer to the left edge across both views
- simplified cleanup after gatekeeper review by removing extra plumbing
and residue
## Screenshots
### Before
<img width="581" height="341" alt="image"
src="https://github.com/user-attachments/assets/ec47b256-749a-4afc-8879-aaf33f0b46b6"
/>
### After
<img width="470" height="586" alt="image"
src="https://github.com/user-attachments/assets/7258a5a2-47c4-408d-84bc-1b497761c7ad"
/>
## Architectural approach
This change intentionally uses:
- shared policy in
`packages/ui/src/components/tool-call/diff-render.tsx`
- shared helper/measurement logic in
`packages/ui/src/components/diff-viewer.tsx`
- mode-specific applicators where unified and split DOM differ
- CSS for shared visual spacing and alignment cleanup
The goal was to keep the implementation architecturally clean and avoid
building separate duplicated compact-diff features for:
- mobile vs desktop
- unified vs split
Instead, the feature shares one compact-diff concept and only diverges
where the upstream diff DOM requires separate handling.
## Files changed
- `packages/ui/src/components/tool-call/diff-render.tsx`
- `packages/ui/src/components/diff-viewer.tsx`
- `packages/ui/src/styles/messaging/tool-call.css`
- `packages/ui/src/types/message.ts`
## Validation
Manual validation was performed in the running UI.
Verified manually:
- compact unified gutters on mobile
- compact unified gutters on desktop
- compact split gutters on desktop
- tighter operator alignment in both modes
Also verified:
- `npm run typecheck` passes
## Notes
- This PR is intended to address the compact diff layout problem
described in the related issue.
- Diff-specific CSS still lives in `tool-call.css`; future extraction
into a smaller dedicated stylesheet is possible but not required for
this change.
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
Closes#261
## Summary
- improve startup remote URL selection when the server binds to
`0.0.0.0`
- print additional reachable remote URLs instead of advertising only the
first external address
- add targeted tests for address ordering and advertisability behavior
## Problem
When CodeNomad was started with `--host 0.0.0.0`, the CLI chose the
first external IPv4 address it discovered and displayed only that one as
the remote URL.
On Windows machines with WSL, Hyper-V, Docker, or other virtual
adapters, that often surfaced a virtual `172.x.x.x` address even though
a more useful LAN address such as `192.168.x.x` was also reachable and
usable from other devices.
That made remote access look broken or confusing even though the server
itself was accessible.
## What changed
- reuse the resolved network-address list for both:
- primary remote URL selection
- startup logging of additional reachable URLs
- choose the primary remote URL from the **advertisable** external
addresses instead of any external address
- print `Other Accessible URLs` when multiple useful remote URLs are
available
- avoid hard-coding a preference like `192.168 > 10 > 172`
- suppress link-local `169.254.*` addresses from user-facing advertised
URLs
- add tests covering:
- stable ordering across RFC1918 address ranges
- link-local addresses being non-advertisable
- link-local-first discovery not stealing the primary LAN URL
## Why this approach
This keeps address derivation in the network-address resolver layer and
limits `index.ts` to startup wiring and presentation.
It also fixes the misleading terminal output without redesigning binding
behavior, TLS behavior, or the server API contract.
## Validation
- `npm run typecheck --workspace @neuralnomads/codenomad`
- `npx tsx --test
'.\\src\\server\\__tests__\\network-addresses.test.ts'`
## Notes
- this change is intentionally focused on selection and presentation of
reachable addresses
- it does not attempt a broader virtual-adapter classification policy
beyond suppressing clearly low-value link-local addresses in user-facing
output
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
Preserve retry metadata from session.status events so the session list and header can show a live retry countdown with context. Notify users when a session enters retry and reuse the existing error styling so retrying feels actionable without losing the current badge layout.
## Summary
- add a per-session Yolo mode toggle for permission prompts and persist
its state
- move the control into the Status tab with clearer copy, an info
tooltip, and a visible header badge when it is enabled
- auto-accept queued permissions for any yolo-enabled session in the
instance, not only the currently focused session
## Why
- keeps this risky mode explicit and easy to audit from the session
status area
- matches the expected multi-session desktop behavior when several
sessions stay active in parallel
## Testing
- npm run typecheck --workspace @codenomad/ui
- npm run build --workspace @codenomad/ui
Closes#18
## Summary
- add server-backed speech capabilities and transcription endpoints plus
UI settings for speech configuration
- add push-to-talk prompt voice input with microphone controls,
transcription insertion, and browser capability gating
- keep prompt controls aligned by restoring right-side nav placement and
moving the mic beside the expand control
# feat(i18n): Hebrew locale + full RTL support
## Summary
This PR adds full Hebrew (he) locale support to the UI, including a
complete translation of all user-facing strings and comprehensive RTL
layout support across all components.
## What was done
### Hebrew translation
- Full translation of all i18n message files for the `he` locale (17
translation files)
- Registered the language in the i18n system and the language picker
### RTL support
- Automatic direction detection (`dir="rtl"`) when Hebrew is selected
- Replaced physical CSS properties (`left`/`right`) with logical
equivalents (`inline-start`/`inline-end`) across the project
- Fixed resize direction, file path alignment, and textarea padding
- Fixed navigation button positioning in textarea for RTL
- Fixed scrollbar direction in RTL
- Fixed code block direction and selector alignment
- Fixed Monaco editor direction in the file viewer
- Auto-detect text direction in reasoning block (`dir="auto"` +
`unicode-bidi: plaintext`)
### Adapted components
- `session-layout` — sidebar and resize handle
- `prompt-input` — text direction and buttons
- `message-base` — message blocks and reasoning
- `message-timeline` — timeline bar
- `right-panel` — right side panel
- `tool-call` — tool call display
- `settings-screen` — settings page
- `selector` — selection component
- `instance-shell` — main shell
## New files
```
packages/ui/src/lib/i18n/messages/he/
advancedSettings.ts
app.ts
commands.ts
dialogs.ts
filesystem.ts
folderSelection.ts
index.ts
instance.ts
loadingScreen.ts
logs.ts
markdown.ts
messaging.ts
remoteAccess.ts
session.ts
settings.ts
time.ts
toolCall.ts
```
## Suggested testing
- Switch language to Hebrew and verify all strings are translated
- Verify RTL layout is correct across all screens (session, settings,
file viewer)
- Verify that English text inside a reasoning block is displayed LTR
- Switch back to English and verify everything returns to LTR
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Shantur Rathore <i@shantur.com>
## What and why
CodeNomad had no RTL (right-to-left) support, so users writing in Hebrew
or Arabic would see their messages displayed left-to-right — misaligned
text, broken reading flow, wrong punctuation placement.
This PR adds automatic direction detection to all elements that display
user or model text. The browser detects direction from the first strong
character in each text block: Hebrew/Arabic → RTL, Latin/code → LTR. No
configuration needed — it just works per message, per paragraph.
## Technical notes
The natural fix is `dir="auto"` on the containing elements. However,
Chromium does not propagate direction detection from a parent `<div>`
into its `<p>` children — so Hebrew inside `<p>` rendered via
`innerHTML` (as markdown is) was still detected as LTR. The fix is to
apply `unicode-bidi: plaintext` via CSS directly on the block-level
elements (`p`, `li`, headings, etc.), which has the same auto-detection
semantics but applies per element.
## Summary
- Add `dir="auto"` to all elements containing user-generated or
model-generated text (message content, prompt input, session names, tool
outputs) so the browser auto-detects text direction
- Add `unicode-bidi: plaintext` via CSS to markdown block elements (`p`,
`li`, headings, `blockquote`, `td`/`th`) to fix per-paragraph RTL
detection in Chromium (where `dir="auto"` on a parent div does not
recurse into block children)
- Convert physical CSS properties to logical equivalents in
`markdown.css`: `border-left` → `border-inline-start`, `padding-left` →
`padding-inline-start`, `text-align: left` → `text-align: start`,
`margin-left` → `margin-inline-start`
## Affected components
- `markdown.tsx` — main markdown renderer
- `message-part.tsx` — text part wrapper and plain-text fallback
- `message-item.tsx` — message body and error blocks
- `prompt-input.tsx` — user input textarea
- `session-list.tsx` — session titles in sidebar
- `session-rename-dialog.tsx` — session rename input
- `instance-welcome-view.tsx` — Resume Session dialog
- `tool-call/markdown-render.tsx` — tool output markdown fallback
- `tool-call/ansi-render.tsx` — ANSI output
- `tool-call/diagnostics-section.tsx` — diagnostic messages
## Test plan
- [ ] Send a Hebrew-only message → text right-aligned
- [ ] Send a mixed Hebrew + English message → correct per-paragraph
direction
- [ ] Message containing a code block → code stays LTR
- [ ] Type Hebrew in the prompt textarea → input flows right-to-left
- [ ] Hebrew session name in sidebar → right-aligned
- [ ] Hebrew session name in Resume Session dialog → right-aligned
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
### Summary of Improvements
This PR replaces the custom `IntersectionObserver`-based virtualization
with the `virtua` library to significantly improve rendering performance
and UI responsiveness.
### 🚀 Performance Results
Verified using `session-performance.test.ts`:
- **Rendering**: 2000 messages rendered in **16.90ms**.
- **Huge Conversation**: 10,000 messages processed in **0.80ms**.
- **Session Switching**: Average switch time reduced to **0.58ms**
(virtually zero lag).
### 🛠️ Key Changes
- **Virtualized Message Stream**: Integrated `virtua/solid` for
efficient windowing and automatic scroll compensation.
- **Floating Scroll Controls**: Applied `position: absolute` and
`pointer-events: none` to the list controls to ensure
scroll-to-top/bottom buttons float correctly over the message area
without blocking interactions.
- **Package Synchronization**: Updated `virtua` and SDK dependencies,
with a fully synchronized `package-lock.json` for stable builds.
### 🎥 UI Verification
https://github.com/user-attachments/assets/24e483a3-8be6-4ac4-a431-d719f2015f4e
- **Smooth Scrolling**: Verified that rendering gaps are eliminated
during fast scrolls.
- **Position Retention**: Scroll positions are preserved when switching
between sessions.
> [!NOTE]
> Detailed performance gains and layout fixes are isolated to the
`virtua` implementation and core package updates, following the
requested cleanup.
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
Complete re-review of PR #188 (commits 224cab6 feature + 2c27fc5 perf/i18n follow-up). Gatekeeper focus: standards, correctness, perf/complexity, and translation completeness.
What this changes (pre -> post)
Pre: timeline primarily navigation/hover preview; bulk delete selection message-level and token metrics tied to backend assistant output tokens (missing tool payload weight).
Post: segment-level timeline selection + range (Shift) + toggle (Ctrl/Meta) + mobile long-press; histogram ribs overlay showing relative + absolute (~10k cap) token weight; assistant-turn grouping to avoid adjacency bugs; bulk-delete toolbar shows Before / Selection / After token pills.
Code standards / correctness
OK: Solid signal/memo/effect patterns with cleanup; no obvious lifecycle leaks. Grouping avoids adjacency overlap by mapping messageId to turns.
Fix: selection-id stability is mitigated by pruning stale ids after segment rebuilds; long term stable ids from part ids/toolPartIds remain recommended.
Fix: token counts now share getPartCharCount in both x-ray overlay and bulk-delete toolbar, keeping estimates consistent with live store updates.
Performance / complexity
OK: O(n^2) hotspots removed for liveSegmentChars and selectedTokenTotal. groupRole + deleteUpTo hover checks now memoize messageId sets/maps.
Note: getPartCharCount can be heavy for large tool payloads but remains gated behind selection mode.
CSS / UI integration
Fix: x-ray token label now uses theme tokens instead of hard-coded colors. Delete toolbar now uses menu-based controls with selection-mode toggle.
i18n
Fix: selection hint now renders Cmd/Ctrl via localized modifier placeholder; all locales updated.
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>
Reverse the message stream scroll layout so the viewport naturally starts at the newest messages and keeps older content virtualized. Use sentinel-based edge chasing to make jump-to-top/bottom land reliably despite VirtualItem mounts.
Adds a dispose instance action to the instance info view, POSTing to /instance/dispose and rehydrating per-instance stores; also handles server.instance.disposed events and adds danger button styling.