## Summary
- fix the sticky-bottom state where dragging the scrollbar to the bottom
makes `PageUp` jump to the previous timeline block and then snap
immediately back down
- keep the change scoped to `virtual-follow-list.tsx`, where follow
mode, scroll intent, and bottom pinning are coordinated
## Root Cause
The list only disabled follow mode when it saw an explicit local "user
intent" signal. After reaching the bottom through the native scrollbar,
`PageUp` could move the viewport without tripping that path, so the next
render notification re-enabled the bottom snap immediately.
## Validation
- `npx tsc --noEmit --project packages/ui/tsconfig.json`
- `npm run build --prefix packages/ui`
- manual desktop test: `PageUp` works again from the bottom sticky state
# Git Changes PR Review Context
Fixes: #310
## Purpose of this document
This document is intended to give a PR reviewer or gatekeeper enough
neutral context to review the Git Changes feature series accurately.
## BEFORE/AFTER SNAPSHOT:
<img width="835" height="1163" alt="image"
src="https://github.com/user-attachments/assets/463d6f8c-1a6b-4cf0-8ab8-44a92c534ca5"
/>
It distinguishes:
1. the intended scope of the work
2. implementation choices that were deliberate
3. behaviors that were explicitly tested and accepted during development
4. remaining follow-up areas that were not part of the required intent
It should not be treated as a request to approve the PR automatically.
It exists to reduce false-positive review findings caused by missing
context.
---
## High-level scope
The work in this series refactors and extends the existing `Git Changes`
tab in the right panel.
The intended feature scope includes:
1. grouped staged / unstaged change presentation
2. correct section-aware diff loading
3. per-file stage / unstage controls
4. commit message compose box and commit action for staged changes
5. prompt-context insertion from the Git diff viewer
6. auto-refresh behavior that reduces dependence on the manual refresh
button
This work is intentionally implemented inside the existing Git Changes
vertical slice rather than as a new SCM subsystem.
---
## Files and areas intentionally changed
### Server / API surface
The following server areas were intentionally extended:
1. `packages/server/src/api-types.ts`
2. `packages/server/src/events/bus.ts`
3. `packages/server/src/server/http-server.ts`
4. `packages/server/src/server/routes/workspaces.ts`
5. `packages/server/src/workspaces/git-status.ts`
6. `packages/server/src/workspaces/git-mutations.ts`
7. `packages/server/src/workspaces/worktree-directory.ts`
8. `packages/server/src/workspaces/instance-events.ts`
### UI surface
The following UI areas were intentionally extended:
1. `packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx`
2. `packages/ui/src/components/instance/instance-shell2.tsx`
3.
`packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx`
4.
`packages/ui/src/components/instance/shell/right-panel/git-changes-model.ts`
5.
`packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx`
6. `packages/ui/src/components/instance/shell/right-panel/types.ts`
7. `packages/ui/src/components/instance/shell/storage.ts`
8. `packages/ui/src/components/prompt-input.tsx`
9. `packages/ui/src/components/prompt-input/types.ts`
10. `packages/ui/src/components/session/session-view.tsx`
11. `packages/ui/src/lib/api-client.ts`
12. `packages/ui/src/lib/i18n/messages/*/instance.ts`
13. `packages/ui/src/styles/panels/right-panel.css`
---
## Intentional product and architecture decisions
The following outcomes were deliberate and should not be flagged as
issues merely because they exist.
### Git status / diff architecture
1. The UI does not rely only on the proxied OpenCode `file.status()`
payload.
2. CodeNomad adds server-backed worktree Git status and diff endpoints
to expose staged / unstaged semantics correctly.
3. Server-backed worktree mutation endpoints were added for:
- stage
- unstage
- commit
4. The existing event bus / SSE channel is reused for Git invalidation,
instead of adding a bespoke invalidation route.
### Git Changes UI structure
1. The file list is grouped into:
- `Staged Changes`
- `Changes`
2. Both sections are collapsible.
3. Section open state is persisted.
4. The same file may appear in both sections when Git state genuinely
requires that.
5. Rows are filename-first, with parent path as secondary text.
6. Rows are intentionally compact compared to the original flat list.
### Diff behavior
1. Diff loading is section-aware.
2. Deleted files are supported in grouped mode.
3. Binary files are treated as non-line-oriented in the diff viewer.
4. Binary diffs suppress line-based prompt-context affordances.
### Stage / unstage / commit workflow
1. Stage and unstage are per-file row actions.
2. Bulk stage-all / unstage-all was intentionally not added.
3. The commit compose box is intentionally rendered inside the `Staged
Changes` section.
4. The commit button is intentionally overlaid inside the commit input
area.
5. The current commit compose flow is minimal by design:
- no push
- no amend flow
- no branch management
### Prompt-context insertion
1. Prompt insertion is intentionally an HTML comment marker, not a full
diff payload.
2. The expected inserted form is:
`<!-- Git change context: <path> lines X-Y -->`
3. The trigger UI is intentionally a seam/gutter action in the Monaco
diff viewer, not a toolbar button.
### Row action reveal behavior
1. Stage / unstage row actions are intentionally hover-revealed on
hover-capable layouts.
2. The row action reveal intentionally uses:
- delayed hide
- slight stats fade/shift
- compact idle width
3. On non-hover layouts, the action remains visible for reliability.
### Auto-refresh behavior
The accepted refresh model is intentionally hybrid:
1. refresh on Git Changes tab activation
2. 20-second polling only while the Git Changes tab is active
3. immediate invalidation from completed raw tool events for:
- `write`
- `edit`
- `apply_patch`
This hybrid model is intentional. Polling remains as a fallback even
after tool-event invalidation.
---
## Behaviors explicitly tested during development
The following behaviors were explicitly exercised during development and
used to guide fixes.
### Grouped staged / unstaged behavior
1. files appear in the correct staged / unstaged sections
2. section collapse / expand works
3. collapse state persists
4. line counts are section-specific
### Diff behavior
1. staged diff loads differently from unstaged diff
2. deleted-file handling was verified and corrected
3. binary-file rendering was corrected to avoid line-oriented behavior
4. untracked binary files no longer report fake text line counts
### Mutation behavior
1. per-file stage works from `Changes`
2. per-file unstage works from `Staged Changes`
3. stage / unstage selection remapping was exercised and corrected
4. unborn-repo unstage behavior was explicitly hardened
### Prompt-context behavior
1. selected line / range insertion was tested
2. button placement in the Monaco seam/gutter was iterated and verified
### Auto-refresh behavior
1. tab-activation refresh was tested
2. 20-second active-tab polling was tested
3. raw completed tool invalidation was tested in the running UI for:
- `write`
- `edit`
- `apply_patch`
4. stale async overwrite and stale selection restoration bugs were found
and fixed through review/testing
---
## Review findings that were investigated and are no longer intended
blocker topics
The following areas were previously raised by strict reviews and then
either fixed or determined to be acceptable within scope.
### Fixed in the current series
1. duplicate stage / unstage firing
2. stale diff response overwriting newer selection
3. passive refresh restoring a stale selection
4. instance-wide invalidation overreach
5. selected diff staying stale after tool invalidation
6. worktree-switch status races
7. unhandled rejection risk from async invalidation publication
8. queued invalidation intent being lost during in-flight refresh
9. `git-diff` path traversal / absolute path boundary issue
### Investigated and considered non-blocking within current intent
1. split add/delete presentation for tracked rename behavior
- this was compared against VS Code behavior during manual testing
- no stage/unstage corruption was observed in the tested flow
- this is currently treated as a representation tradeoff, not a proven
blocker
---
## Remaining non-blocker follow-up areas
The following are still reasonable follow-up topics, but they were not
part of the required blocker-fix scope.
1. normalize directory-to-worktree matching more aggressively on Windows
so tool invalidation works more reliably from nested directories or
path-format variations
2. improve keyboard discoverability of hover-revealed stage / unstage
actions
3. reserve textarea space for the overlaid commit button if the overlay
tradeoff is reconsidered
4. reduce size/complexity in:
- `RightPanel.tsx`
- `right-panel.css`
5. tighten raw SSE tool-event parsing into a more explicit helper if
that event bridge grows further
These follow-ups should not be interpreted as evidence that the core
implementation is incomplete unless a reviewer finds a new concrete
failure.
---
## Suggested review focus
If a gatekeeper or reviewer is evaluating this PR, the most useful focus
areas are:
1. whether staged / unstaged behavior is correct for normal Git
workflows
2. whether the new server worktree Git endpoints remain narrowly scoped
3. whether auto-refresh remains bounded to the active Git Changes
context
4. whether the explicit fixes for stale async behavior and invalidation
races are sufficient
5. whether any unintentional server boundary broadening or state
corruption remains
Less useful review topics, unless tied to a concrete failure, are:
1. preference disagreements with accepted prompt insertion format
2. preference disagreements with the overlaid commit button placement
3. preference disagreements with keeping polling fallback alongside tool
invalidation
4. objections to server-backed Git endpoints purely because they add
surface area
---
## Summary
This series intentionally evolves the existing Git Changes tab into a
more complete source-control workflow for:
1. grouped staged / unstaged inspection
2. section-aware diffs
3. per-file staging and unstaging
4. commit composition for staged changes
5. prompt-context insertion from Git diffs
6. bounded auto-refresh for both passive viewing and agent-driven file
mutations
The intended review standard is to find concrete correctness, layering,
or maintenance problems that remain after this series — not to re-argue
the already accepted product choices listed above.
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
## 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 .
# PR Draft: Fix sticky auto-scroll during streaming chat responses
Fixes#308
## Summary
This change makes chat auto-scroll easier to escape while assistant
output is still streaming.
The goal is to stop the viewport from repeatedly pulling the user back
toward the bottom once they begin scrolling upward to inspect earlier
content.
## Why
Before this change, streaming updates could keep reasserting
bottom-follow behavior during active rendering. That made auto-scroll
feel sticky and forced users to scroll repeatedly or forcefully just to
review earlier parts of an in-progress response.
The intended behavior is simpler: once the user scrolls upward to leave
follow mode, the UI should respect that decision instead of fighting it
during subsequent stream updates.
## What Changed
1. Removed render-time force-bottom behavior from the shared
follow-scroll helper path.
2. Updated streamed reasoning output to restore scroll without forcing
the viewport back to the bottom.
3. Updated streamed tool-call output to use the same non-forcing restore
behavior.
## Scope Boundaries
Included:
- Sticky auto-scroll behavior during streamed chat output
- Shared follow-scroll behavior used by streamed nested panes
- Reasoning and tool-call streaming paths that reused the same forced
follow behavior
Not included:
- A full rewrite of the virtualized message list follow model
- Broader scroll UX changes outside the streaming follow/escape behavior
- Unrelated UI or plugin configuration changes in the worktree
## Technical Notes
The core problem was not basic auto-scroll itself, but a render-time
path that could keep forcing bottom-follow behavior while new streamed
content was arriving.
That meant a user's attempt to scroll upward could be overridden
repeatedly by subsequent stream updates, which is why the auto-scroll
felt sticky. The fix removes that override and keeps render-time
restoration dependent on the current follow state instead.
## Files Changed
- `packages/ui/src/lib/follow-scroll.tsx`
- `packages/ui/src/components/message-block.tsx`
- `packages/ui/src/components/tool-call.tsx`
## Verification
Performed:
1. Reproduced the sticky auto-scroll behavior with a long multi-line
streaming response.
2. Verified that scrolling upward during streaming now disengages follow
more naturally in the affected streamed panes.
3. Ran `npm run typecheck --workspace @codenomad/ui`.
4. Ran `npm run build --workspace @codenomad/ui`.
Build note:
- The UI typecheck passes.
- The UI build succeeds.
- The build still emits existing third-party and chunk-size warnings
unrelated to this change.
## Risks and Follow-up
1. The broader scroll-follow model is still more heuristic-heavy than
ideal, so there may be future follow-up work to simplify it further.
2. This PR intentionally applies the smallest targeted fix to the known
snap-back path instead of rewriting the full chat scroll system.
---------
Co-authored-by: Shantur Rathore <i@shantur.com>
- Track messageInfoVersion in cache signature to rebuild when tokens arrive via SSE
- Read tokens from step-finish part directly (embedded in SSE events)
- Simplify available tokens to show full context window when no explicit input limit
Session diffs now use a compact patch field instead of storing full
before/after content. Added parsePatchToBeforeAfter utility to extract
before/after from unified diff format, and updated MonacoDiffViewer to
accept patch prop as alternative to before/after strings.
Send synthetic session notifications when background processes finish, fail, stop, or terminate so the originating agent can react without polling. Hide synthetic text-only prompts from the UI stream so operational notifications stay out of the visible transcript.
## Summary
- preserve the current prompt text when dismissing the `@` mention/file
picker with `Esc`
- let `Enter` fall back to normal prompt submission when the mention
picker is open but there is no selectable result
## Verification
- source inspection of the prompt input and picker flow
- local `npm run typecheck --workspace @codenomad/ui` is blocked in this
environment because workspace dependencies are not installed
--
Yours,
[CodeNomadBot](https://github.com/NeuralNomadsAI/CodeNomad)
Co-authored-by: Shantur Rathore <i@shantur.com>
## Summary
- Wraps store-proxied array iteration in `untrack()` in two
`createEffect` blocks and one `createMemo` in `message-section.tsx` to
prevent SolidJS from creating O(n) per-element reactive subscriptions on
every run
- Replaces `ids.includes()` with `Set.has()` for O(1) cleanup lookups in
the part-count tracking effect
## Problem
Two `createEffect` blocks in `message-section.tsx` iterate the
`messageIds()` store proxy array inside a tracked reactive context. This
causes SolidJS to create **O(n) per-element subscriptions** on every
run. When any element changes, all n subscriptions fire, re-running the
entire effect — resulting in **O(n²) total work**.
Additionally, the cleanup loop in the part-count tracking effect uses
`ids.includes(trackedId)` which is O(n) per tracked ID, compounding to
O(n²).
For long-running sessions with large message history (e.g. 7569
messages), this caused **~4.8 seconds of input latency** when sending a
new prompt.
## Fix
1. **Timeline sync effect (~line 738):** Wrap entire body in
`untrack()`, replace `ids.slice()` with `[...ids]` to snapshot without
proxy tracking
2. **Part-count tracking effect (~line 891):** Wrap iteration in
`untrack()`, replace `ids.includes()` with `new Set(ids).has()` for O(1)
lookups
3. **`lastAssistantIndex` memo:** Read message records via `untrack()`
to avoid O(n) subscriptions on part-level updates
## Result
On a 7569-message session: prompt input latency reduced from **~4.8s to
~42ms** (114x improvement).
## 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.
Only cache markdown HTML after Shiki has the required fence languages loaded so virtualized assistant messages can re-render with syntax highlighting when remounted.
## 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
Let users refresh a session transcript from the sidebar without reopening it. Reuse the existing forced message loading path so the reload behavior stays aligned with normal session hydration.
## Summary
- escape raw HTML when rendering user message markdown so prompt input
is shown as text instead of injected HTML
- keep assistant and tool markdown behavior unchanged by scoping the
escape behavior to user messages
- update markdown cache keys so escaped and non-escaped render output do
not collide
## Verification
- `npm run typecheck --workspace @codenomad/ui` *(fails in this
workspace because frontend dependencies are not installed)*
- `npm run build --workspace @codenomad/ui` *(fails in this workspace
because `vite` is not installed)*
--
Yours,
[CodeNomadBot](https://github.com/NeuralNomadsAI/CodeNomad)
Co-authored-by: Shantur <shantur@Mac.home>
## Summary
- Adds file writing capability to Monaco editor in the file viewer
- Implements writeFile API on the server for workspace files
- Integrates save functionality into the file viewer UI with proper
state management
## Bug Fixes (Review Feedback)
- Fixed failed save discarding edits when switching files - now checks
save result and only proceeds if successful
- Fixed refresh overwriting dirty editor state - now prompts for
confirmation before discarding edits
- Fixed save button unable to save empty files - changed check from `if
(content)` to `if (content !== undefined && content !== null)`
- Added agent edit conflict detection - when agent edits file while user
has unsaved changes, shows conflict dialog with Overwrite/Cancel options
- Fixed dialog appearing behind unpinned sidebar - increased alert
dialog z-index to z-100
## Related Issues
- Closes#251
---------
Co-authored-by: Jess Chadwick <jchadwick@gmail.com>
## 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>