Skip to content

feat(ui): add message timing metrics#357

Open
pascalandr wants to merge 36 commits into
NeuralNomadsAI:devfrom
Pagecran:feat/message-timing-metrics
Open

feat(ui): add message timing metrics#357
pascalandr wants to merge 36 commits into
NeuralNomadsAI:devfrom
Pagecran:feat/message-timing-metrics

Conversation

@pascalandr
Copy link
Copy Markdown
Contributor

@pascalandr pascalandr commented Apr 23, 2026

Fixes #297

Summary

  • show total assistant response duration next to the existing message timestamp using only explicit OpenCode message timing
  • show reasoning duration on reasoning cards only when OpenCode provides explicit part timing
  • remove all client-side timing inference from updated timestamps, next-part boundaries, and message-completion fallback

Why

Client-derived durations are not reliable indicators of actual model thinking or processing time.

This revision keeps the UI scoped to explicit server-side timing data that OpenCode already provides, which matches the maintainer feedback on this PR.

Validation

  • node --experimental-strip-types --test "packages/ui/src/lib/message-timing.test.ts"
  • npm run build --workspace @codenomad/ui

@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented Apr 23, 2026

Gatekeeper review for the latest PR state:

What I reviewed

  • Request understood as: do a ruthless PR gatekeeper pass on the current branch, focused on regressions and better implementation options for the new message timing metrics.
  • Reviewed the changed code paths in packages/ui/src/lib/message-timing.ts, packages/ui/src/lib/message-timing.test.ts, packages/ui/src/components/message-item.tsx, packages/ui/src/components/message-block.tsx, and packages/ui/src/styles/messaging/message-base.css.

Findings

  • No blocking regressions found in the current implementation.
  • No clearly better implementation jumped out that would justify changing this PR. The timing logic is kept in one helper module as requested, and the UI integration stays narrow.

What I checked closely

  • packages/ui/src/lib/message-timing.ts:17-95
    Centralized timing extraction is small and coherent. The completion fallback rules are conservative, and reasoning duration inference prefers explicit values before inferring from the next timed part or message completion.
  • packages/ui/src/components/message-item.tsx:142-145 and :565-569
    Assistant-only total duration display is scoped correctly and does not affect user messages.
  • packages/ui/src/components/message-block.tsx:760-779, :1398-1405, and :1600-1604
    Reasoning cards receive inferred durations without spreading timing logic into the component tree.

Validation

  • node --experimental-strip-types --test "packages/ui/src/lib/message-timing.test.ts" passed.
  • npm run build --workspace @codenomad/ui could not be completed in this workspace because the environment is missing the vite package, so I could not fully verify build-level regressions from here.

Residual risk

  • The helper tests cover the inference rules well, but component-level rendering is still implicitly covered rather than directly tested. If you want this even tighter, the next incremental improvement would be a UI-level test around assistant header timing and reasoning-card timing display.

Gatekeeper verdict

  • Approved from a code-quality and regression-review standpoint, with the only unresolved item being build verification blocked by missing local dependencies in this environment.

--
Yours,
CodeNomadBot

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 26, 2026

@pascalandr - Are these times provided by OpenCode or its being calculated in CodeNomad based on request and response?
If it's latter, then these times have no value at all, as its not thinking time or anything, it can be the time for the request reaching, queuing, and processing.

@pascalandr
Copy link
Copy Markdown
Contributor Author

pascalandr commented Apr 26, 2026

@pascalandr - Are these times provided by OpenCode or its being calculated in CodeNomad based on request and response? If it's latter, then these times have no value at all, as its not thinking time or anything, it can be the time for the request reaching, queuing, and processing.

It does use an explicit part duration if one is present, but otherwise it is just derived from the timing metadata we have in the stream.
A better version would be to only show explicit server-side timings, right ?
OpenCode does already provide some server-side timing data for messages and parts, so a basic elapsed duration could be meaningful.

@shantur
Copy link
Copy Markdown
Collaborator

shantur commented Apr 26, 2026

Agreed, we can look at this PR once OC times are used

@shantur shantur added the needs-work PR needs more work label Apr 26, 2026
@pascalandr
Copy link
Copy Markdown
Contributor Author

Updated this PR to only use explicit OpenCode timing data.

What changed:

  • total assistant duration now only uses explicit message timing (duration / time.duration) or time.created -> time.end
  • reasoning duration now only uses explicit part timing (duration / time.duration) or time.start -> time.end
  • removed all client-side inference from updated timestamps, next-part boundaries, and message-completion fallback

Validation:

  • node --experimental-strip-types --test "packages/ui/src/lib/message-timing.test.ts"
  • npm run build --workspace @codenomad/ui

@pascalandr
Copy link
Copy Markdown
Contributor Author

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented Apr 30, 2026

Gatekeeper review for the latest PR state:

What I understood

  • Request: perform a ruthless gatekeeper pass on the revised PR, specifically checking regressions and whether the implementation now correctly avoids unreliable client-side timing inference.
  • Assumption: the intended behavior is exactly what the updated PR body/comment says: show durations only from explicit OpenCode timing data, not from updated, stream ordering, next-part boundaries, or message-completion fallback.

Findings

  • No blocking regressions found.
  • The revised implementation now matches the maintainer feedback: duration display is limited to explicit duration / time.duration, or explicit start/end timing on the same OpenCode message/part.

What I checked

  • packages/ui/src/lib/message-timing.ts:29-38
    Message duration no longer uses local fallback timestamps or time.updated. It uses explicit duration first, then time.created -> time.end only when both are present on the message.
  • packages/ui/src/lib/message-timing.ts:44-60
    Reasoning duration no longer infers from next timed part or message completion. It only uses explicit part duration or explicit part start/created -> end timing.
  • packages/ui/src/lib/message-timing.test.ts:18-43
    Tests now cover the important negative cases: no message duration from updated, and no reasoning duration from message completion.
  • packages/ui/src/components/message-item.tsx:142-144 and :565-569
    Total duration remains assistant-only and empty durations render nothing.
  • packages/ui/src/components/message-block.tsx:760-779 and :1398-1405
    Reasoning-card duration is passed through the helper and displayed only when formatted duration exists.

Better implementation notes

  • Non-blocking: inferReasoningDurationMs is now a compatibility wrapper around getPartDurationMs, so the name is a bit stale. I would not block this PR on renaming it, because keeping the call sites stable makes the fix smaller.
  • Non-blocking: getMessageCompletedAt is exported but currently unused by changed code. It is harmless, but could be removed if there are no expected near-term callers.

Validation

  • node --experimental-strip-types --test "packages/ui/src/lib/message-timing.test.ts" passed.
  • npm run build --workspace @codenomad/ui could not run to completion in this workspace because vite is missing from local dependencies here (ERR_MODULE_NOT_FOUND: Cannot find package 'vite'). This appears to be an environment/dependency-install issue, not a code failure from this PR.

Gatekeeper verdict

  • Approved from code-quality and regression-review standpoint.
  • The prior core concern about client-derived durations has been addressed in the latest PR state.

--
Yours,
CodeNomadBot

pascalandr and others added 3 commits May 1, 2026 23:04
Resolve the message-block import conflict between the message timing metrics and the session search updates from dev.

This keeps the OpenCode timing helpers wired into reasoning cards while preserving the new session search match typing from dev.

Validated with the message timing unit tests, UI typecheck, and UI build after resolving the merge.
Remove the launcher tagline from the home screen and normalize the Open Sidecar action spacing so the action stack uses consistent vertical rhythm.

This keeps the recent workspace area higher on the page without changing launcher behavior or the existing folder, clone, sidecar, and remote actions.
@pascalandr pascalandr removed the needs-work PR needs more work label May 9, 2026
OfflinePing and others added 15 commits May 9, 2026 21:47
…ncluding subagents (NeuralNomadsAI#415)

## Summary

- Adds three new chips (Total In, Total Out, Total Cost) to the
`ContextUsagePanel` that aggregate usage across the parent session and
all child sessions (subagents and forks)
- Child session messages are proactively loaded to ensure historical
data appears without manually navigating to each child
- Chips respect the existing `showUsageMetrics` preference toggle and
are hidden when the session has no children
- Reactive computation is scoped to the current instance to avoid
unnecessary re-runs

## Changes

| File | Change |
|------|--------|
| `packages/ui/src/components/session/context-usage-panel.tsx` | Adds
`threadTotals` memo, proactive `loadMessages` effect, and three total
chips |
| `packages/ui/src/lib/i18n/messages/*/settings.ts` (7 locales) | Adds
`totalInput`, `totalOutput`, `totalCost` i18n keys |

## Screenshots
When viewing a parent session with subagents, the panel shows:
```
[Input] [Output] [Cost] | [Total In] [Total Out] [Total Cost]
```

## Notes
- This PR was primarily AI-generated and has gone through several
iterative review and refinement steps
- Built and type-checked against `upstream/dev`
- Needs review and approval from the CodeNomad team

Closes NeuralNomadsAI#401

---------

Co-authored-by: Pascal André <pascalandr@gmail.com>
Use the dedicated session children endpoint when refreshing the session list so parent rows and aggregate usage are based on the complete child set rather than the recent /session window.

Move thread usage totals into session state and refresh them whenever session info changes, including SSE-driven message updates. Parent message loading now also hydrates child messages from the store while preserving existing loaded-message guards.

Validation: npm run typecheck --workspace @codenomad/ui
)

## Summary

This PR keeps only the UI-side fix for primary agent selection in
CodeNomad.

It aligns the primary agent selector with OpenCode's behavior by:
- filtering primary-session agents through a shared selectable-primary
rule
- avoiding re-inserting an invalid current agent back into the primary
selector
- using the same primary-agent rule when choosing the default agent for
a newly created session

## Why

The earlier investigation showed that the config-loading problem was
actually rooted in OMO's handling of `OPENCODE_CONFIG_DIR`, not in
CodeNomad itself.

However, there was still an independent UI issue in CodeNomad:
- the primary selector could keep showing an agent that should no longer
be selectable
- new sessions could derive their default agent from a looser filter
than the one used by the selector

This PR keeps only that UI-side fix.

## Changes

- `packages/ui/src/components/agent-selector.tsx`
- use a shared `isSelectablePrimaryAgent` helper for primary-session
filtering
- stop force-inserting the current invalid agent back into the primary
selector list

- `packages/ui/src/stores/session-api.ts`
- use the same `isSelectablePrimaryAgent` helper when selecting the
default agent for a new session

- `packages/ui/src/types/session.ts`
  - add `isSelectablePrimaryAgent(agent)` helper

## Scope

This PR intentionally does **not** include any config merge / config
copy workaround related to `OPENCODE_CONFIG_DIR`.

That part was removed so this PR stays narrowly focused on the
primary-agent selection behavior only.

## Validation

- verified changed files are clean in editor diagnostics
- confirmed this is a minimal UI-only diff

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
)

## Summary
- Split out from NeuralNomadsAI#422 as the YOLO-only permission fix.
- Moves YOLO auto-accept draining out of `InstanceShell` render effects
and into the permission queue flow.
- Keeps behavior unchanged: YOLO remains per-session and replies with
`once` only.
- Keeps in-flight cleanup for auto-accept attempts so duplicate sends
are guarded outside UI render timing.

## Why
In YOLO mode, the app could still appear to wait on a permission or
block the UI, even though auto-accept was enabled and the permission was
already queued.

The auto-accept drain was tied to an `InstanceShell` render effect, so
the reply path depended on a specific UI shell rendering. Draining from
permission sync/enqueue and from the YOLO toggle makes auto-accept run
from the permission queue itself instead of UI render timing.

## Validation
- `git diff --check`
- `npm run typecheck --workspace @codenomad/ui`
## Summary
- Split out from NeuralNomadsAI#422 as the stale-permission-events fix.
- Clears permission UI state immediately after a successful local
permission reply instead of waiting for `permission.replied` SSE.
- Tracks replied permission IDs until a newer pending-permission sync
observes that the server no longer reports them pending.
- Marks SSE `permission.replied` events into the same replied-ID path so
delayed pending events/sync results cannot resurrect prompts that were
just answered.

## Why
A permission prompt could remain on screen after clicking Allow/Deny, or
clicking Allow could look like it did not take effect and require
another click. Reloading could fix the UI, which pointed to stale local
permission state rather than the server still waiting.

The UI receives permission state from local replies, SSE events, and
pending-permission sync. If an older pending event or sync result is
processed after a confirmed reply, the UI can re-add a permission that
was already answered. Replied IDs stay suppressed until a sync started
after the local reply proves the server has dropped that permission from
the pending list.

## Validation
- `git diff --check`
- `node --test packages/ui/src/stores/permission-replies.test.ts`
- `npm run typecheck --workspace @codenomad/ui`
## Summary
- Add a behavior setting to show or hide the message timeline sidebar.
- Remove the timeline layout gutter when the sidebar is disabled.
- Add setting labels for all supported locales.

## Why
user wants a more minimal workspace and a little more horizontal room
without losing the default timeline experience for everyone else.

## Validation
- npm run typecheck --workspace @codenomad/ui
- git diff --check
- npm run build:tauri, then launched the raw release exe

Fixes NeuralNomadsAI#418
## Summary
- Split out from NeuralNomadsAI#422 as the permission/tool-call reconciliation fix.
- Reconciles pending permissions whenever live tool parts update,
matching the existing question re-link path.
- Keeps one message-v2 attachment per server permission ID and
recalculates the active permission from queue order.
- Conservatively merges duplicate or out-of-order permission updates so
known session/message/tool routing metadata is not lost.
- Fixes NeuralNomadsAI#290

## Why

The observed failure shape is that permission prompts can appear
missing, frozen, or attached in unexpected places when permission events
and tool-call parts are observed in different orders. In those cases,
the server-side permission may exist, but the UI can temporarily attach
it globally, attach it to the wrong tool location, or fail to move it
when the matching tool part arrives later.

This PR focuses on the UI-side attachment/order problem: one UI
attachment per server permission ID, re-linking permissions when tool
parts arrive, and preserving known routing metadata across
duplicate/out-of-order updates. It does not attempt semantic
deduplication across different permission IDs that happen to ask for the
same logical approval.

## Validation
- `git diff --check`
- `npm exec --no -- tsx --test packages/ui/src/types/permission.test.ts
packages/ui/src/stores/message-v2/instance-store.test.ts`
- `node --test packages/ui/src/stores/permission-replies.test.ts`
- `npm run typecheck --workspace @codenomad/ui`
## Summary
- Lock Electron remote BrowserWindow titles to the generated
saved-server title.
- Lock Tauri remote webview document titles through the remote
initialization script so native window titles remain distinguishable.
- Preserve the existing title format that includes the saved server name
and host.

Closes NeuralNomadsAI#427

## Validation
- npm run typecheck --workspace @neuralnomads/codenomad-electron-app
- cargo check (packages/tauri-app/src-tauri)
## Summary
- Follow-up to NeuralNomadsAI#395 after the idle badge persistence behavior regressed
around `keepUnseenSubagentIdleStatus`.
- Restores `keepUnseenSubagentIdleStatus` handling across session,
active-session, and instance-tab badge helpers.
- Starts a 5s fade when a viewed idle session/thread is seen, then
clears each exact idle transition after the animation even if focus
moves away.
- Keeps unseen subagent idle badges visible when the preference is
enabled, and keeps aggregate instance badges solid when any other
visible idle contributor is still unseen.

## Validation
- `git diff --check`
- `npm run typecheck --workspace @codenomad/ui`
- `npm run build --workspace @codenomad/ui`
- Gatekeeper re-review: no blocking findings remain for the prior
preference/race findings.

## Notes
- `node --test packages/ui/src/stores/session-status.test.ts` was
attempted, but the repo's extensionless TypeScript imports are not
directly resolvable by Node's test runner.
## Summary
- Return to an existing open project when users choose that from the
already-open folder dialog.
- When users click a recent folder that is already open, ask whether to
switch to the open project or intentionally open another instance.
- Surface a prominent Return to Active Project action so leaving the
home screen no longer depends on the subtle top-right close affordance.
- Keep multi-instance workflows available without requiring users to
remember a separate row-level button.

Fixes NeuralNomadsAI#281

## Why
Users can accidentally open the same workspace several times from the
add-project screen. The app now detects that ambiguity when the
already-open folder is clicked and asks what should happen, instead of
making the user remember a special alternate button ahead of time.

The dialog keeps the copy short: the title states that the project is
already open, and the buttons carry the actual choices.

## Verification
- `git diff --check`
- `npm run typecheck --workspace @codenomad/ui`
- `npm run build --workspace @codenomad/ui`
- `npm run build:tauri`
## Summary
- Add ephemeral per-session web preview mode for opening arbitrary
HTTP(S) URLs inside the session workspace without persisting preview
state across reloads.
- Reuse the SideCar iframe shell by extracting a shared BrowserFrame
with navigation, refresh, path entry, viewport presets, and no-injection
element comment targeting.
- Add authenticated preview proxy routes for HTTP and WebSocket traffic,
sharing lower-level proxy forwarding with SideCars to avoid duplicating
proxy code.
- Localize all new preview and viewport strings across English, Spanish,
French, Hebrew, Japanese, Russian, and Simplified Chinese.

## User-facing behavior
- Users can open a web preview from the session toolbar and toggle back
and forth between chat and preview mode.
- Preview mode replaces only the message stream/timeline area; the
prompt input and attachments stay available below the preview.
- Comment mode lets users select elements in the same-origin proxied
iframe and append structured page/element references to the prompt
draft.
- The generated prompt quotes the page/element reference while leaving
the user's actual comment as normal prompt text.
- Viewport selection supports responsive, desktop, tablet
portrait/landscape, and mobile portrait/landscape canvases, with fixed
sizes isolated inside the iframe scroll container.

## Implementation notes
- Preview sessions are in-memory and keyed by short-lived tokens exposed
through `/previews/:token`.
- Preview proxy responses strip frame-blocking headers and rewrite
same-origin redirect locations back under the preview route.
- SideCarView now composes the shared BrowserFrame, preserving existing
SideCar behavior while gaining shared viewport controls.
- Parent-side iframe DOM access powers hover highlighting and element
metadata extraction, avoiding HTML/script injection for the first
implementation.

## Validation
- `npm run typecheck --workspace @codenomad/ui`
- `npm run typecheck --workspace @neuralnomads/codenomad`

## Notes
- Existing unrelated local changes to `package.json` and
`package-lock.json` were intentionally left out of this PR.
- The initial proxy does not yet rewrite all HTML/CSS asset references;
pages with root-relative assets may need follow-up URL rewriting.
…adsAI#432)

## Summary
- Reuses the directory browser for web file attachment selection and
removes the separate filesystem browser dialog.
- Reads web-selected files through the filesystem browser API so
selection respects unrestricted and configured workspace roots.
- Keeps Electron/Tauri picker selection on the FileList ingestion path
so picked files behave like dropped files with byte-backed attachments.

## Validation
- npm run typecheck --workspace @codenomad/ui
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @neuralnomads/codenomad-electron-app
- cargo check
- git diff --check

## Notes
- Follow-up to NeuralNomadsAI#417; this branch currently includes the commits from
that PR plus the attachment picker parity fix.

---------

Co-authored-by: Pascal André <pascalandr@gmail.com>
## Summary
- Replace the old instance welcome screen with a focused workspace
launching state.
- Move no-session empty content into `MessageSection` as a variant of
the existing empty message-list view.
- Keep the normal bottom prompt available before a session exists and
create the first session from that prompt using draft agent/model
selections.

## Validation
- `npm run typecheck --workspace @codenomad/ui`

## Notes
- Existing local changes in `packages/opencode-config/package.json` and
`packages/opencode-config/package-lock.json` were left uncommitted and
are not part of this PR.
## Summary
- Rename the OpenCode config template into a versioned npm-packable
CodeNomad plugin package.
- Build and package the plugin through the server bundle, with
Electron/Tauri carrying it via existing server resources.
- Replace OPENCODE_CONFIG_DIR injection with JSONC-aware
OPENCODE_CONFIG_CONTENT merging that appends the CodeNomad plugin while
preserving user config.

## Validation
- npm run build --workspace @codenomad/codenomad-opencode-plugin
- npm run prepare-plugin --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @neuralnomads/codenomad
- npm run typecheck --workspace @neuralnomads/codenomad-electron-app
- node --import tsx --test \"src/opencode-plugin.test.ts\"
\"src/workspaces/__tests__/spawn.test.ts\"

## Notes
- Production plugin loading uses an explicit npm file alias for the
packaged tarball.
- Dev loading still references the TypeScript plugin entry directly.

---------

Co-authored-by: Pascal André <pascalandr@gmail.com>
Remove the prominent Return to Active Project button from the folder selection actions area so the home screen no longer shows a redundant project-return call to action.

Drop the active project label plumbing and localized return subtitle strings because the overlay still has the existing close affordance for dismissing the home screen.

Validation: attempted pnpm --filter @codenomad/ui typecheck, but tsc was not available in the current environment.
shantur and others added 16 commits May 12, 2026 11:58
## Summary
- Add measured `narrow` / `medium` / `wide` width steps to the session
center column so chat UI can respond to pane width rather than viewport
width.
- Rework prompt composer controls: clear/expand move into the text
field, narrow panes place auxiliary controls below the textarea, and
stop/send align bottom-right.
- Reduce branded empty-state logo/title size in narrow session panes and
remove the prompt `@ files/agents` overlay hint.

## Validation
- `npm run typecheck --workspace @codenomad/ui`
Move the compact timeline width rule from viewport media queries to the session-center narrow width step. This keeps the timeline compact when the chat pane is narrow due to side panels, even on wide desktop viewports.

Validation: npm run typecheck --workspace @codenomad/ui
## Summary
- Tighten thinking block action spacing so it matches adjacent action
groups.
- Collapse secondary message, thinking, and tool-call actions into a
Kobalte overflow menu only at the session-center narrow width stop.
- Keep single-action cases inline, preserve primary/status controls,
mirror delete hover overlays from menu items, and square the overflow
menu styling.

## Validation
- npm run typecheck --workspace @codenomad/ui
## Summary
- Show attachments added to the no-session draft prompt before a session
exists.
- Create and activate a real session before first-prompt slash commands
or shell commands execute.
- Keep existing session command behavior unchanged by adding an optional
PromptInput command handler.

## Validation
- npm run typecheck --workspace @codenomad/ui
…cs (NeuralNomadsAI#413)

## Summary

Adds a new **Info** section to the Settings screen, giving users quick
visibility into their CodeNomad version, runtime environment, and a way
to collect diagnostic data for bug reports.

Closes NeuralNomadsAI#412

## Changes

- **New component:** `info-settings-section.tsx` — renders three cards:
- **About** — Server/UI version, runtime type (electron/tauri/web),
platform, OS with CPU architecture detection, server URL, workspace root
- **Updates** — Placeholder "Check for updates" button, ready to wire
into the existing dev release monitor (`serverMeta.update`)
- **Diagnostics** — Log scope dropdown (Summary only / Summary +
workspace logs), Copy to clipboard, Download .txt — generates a
structured diagnostic report

- **New styles:** `settings-info.css` — info row layout, select row,
toast feedback, update note

- **Wiring:** Added `"info"` to `SettingsSectionId` union, registered
nav item and routing case in `settings-screen.tsx`, imported CSS in
`controls.css`

- **i18n:** 20 new keys added to all 7 locales (English canonical,
others fall back via existing fallback chain)

## Design decisions

- No server changes needed — version/runtime info comes from existing
`GET /api/meta` endpoint and client-side `navigator` detection
- No sensitive data (API keys, env values) included in diagnostic
reports
- CPU architecture extracted from `navigator.userAgent` for
Linux/Windows/macOS
- Log scope dropdown uses existing Kobalte Select pattern for
consistency

## Verification

- `tsc --noEmit` passes (0 errors)
- `vite build` bundles successfully
- Tauri Rust backend compiles and starts correctly

---

> **Note:** The majority of this implementation was produced through
CodeNomad using DeepSeek v4 Pro and has undergone manual review before
submission. All design, architecture, and code quality decisions were
evaluated by a human reviewer.

---------

Co-authored-by: Shantur Rathore <i@shantur.com>
## Summary
- add a top-edge resize handle so the session composer can be freely
stretched upward while keeping the bottom edge anchored
- switch the expand/shrink control to track the actual composer height,
clamp expansion to the session toolbar boundary, and reset the composer
back to its default height after each send
- preserve the right-side three-column control layout in narrow and
windowed panes so the five utility controls stay outside the input, the
stop button rises with the composer, and the send button remains
bottom-pinned

## Testing
- npm run typecheck --workspace @codenomad/ui
- npm run build --workspace @codenomad/ui

## Notes
- the build still reports the existing `virtua` JSX warning from
`../../node_modules/virtua/lib/solid/index.jsx`; this PR does not
introduce that warning
- local unstaged changes in `.opencode/package-lock.json`, `.sisyphus/`,
and Electron dev-port files were intentionally excluded from this PR
because they are unrelated to the composer height feature

---------

Co-authored-by: Shantur Rathore <i@shantur.com>
…#452)

## Summary
- Make the folder selection home screen scroll vertically when short
viewports cannot fit the primary action column.
- Cap the recent folders/server list to the measured action column
height on desktop so it scrolls internally instead of stretching the
layout.
- Use a focused home layout stylesheet with equal-width desktop columns.

## Validation
- npm run typecheck --workspace @codenomad/ui
- npm run build --workspace @codenomad/ui

## Notes
- Build still emits existing warnings about virtua JSX import source and
large chunks; unrelated to this change.
## Summary
- Trim Electron app packaging to only include runtime shell assets and
remove unused workspace runtime dependencies.
- Copy server resources explicitly, filtering stale build artifacts and
pruning known non-runtime dependency files.
- Bundle only the Node executable instead of the full Node distribution.

## Validation
- Built macOS Electron artifacts with `npm run build:mac --workspace
@neuralnomads/codenomad-electron-app`.
- Ran packaged server CLI smoke test: `dist/bin.js --version` returned
`0.16.0`.
- Ran packaged dependency import smoke test for pruned runtime
dependencies.

## Size Impact
- macOS x64 zip: 161M
- macOS arm64 zip: 161M
- macOS arm64 app bundle: 435M
- Packaged server node_modules: 25M
# Conflicts:
#	packages/ui/src/components/message-block.tsx
#	packages/ui/src/components/message-item.tsx
## Summary
- Stop the right panel from immediately re-opening collapsed Status
sections.
- Keep the existing default expanded sections, including session
changes.

## Why
Users could click Status sub-panels to collapse them, but the UI
immediately expanded them again, making the accordion controls feel
broken.

## Validation
- npm run typecheck --workspace @codenomad/ui
- git diff --check
- npm run build --workspace @codenomad/ui
…AI#461)

## Summary
- Fixes NeuralNomadsAI#460 by changing the prompt control DOM order so tabbing from
the textarea reaches expand before clear.
- Clears prompt text through the textarea native delete path so
Cmd/Ctrl+Z can restore cleared text where the browser supports native
undo.
- Keeps the existing app-state fallback for environments where native
editing commands are unavailable or throw.

## Validation
- npm run typecheck --workspace @codenomad/ui
- Reviewed fallback behavior: `document.execCommand` is checked for
callability and wrapped in `try/catch`, so unavailable native editing
commands still fall back to app-state clearing.

## Manual QA Notes
- In a supported desktop/browser host: type text, click clear, then
press Cmd/Ctrl+Z and confirm text restores.
- Type text, press Tab and confirm focus moves to expand before clear;
pressing Tab again reaches clear.
…s open (NeuralNomadsAI#459)

## Summary

Fixes a mobile UX bug where, with the session list (left drawer) open on
phone layout, the still-visible instance/project tab bar at the top was
non-interactive. Tapping a tab did nothing — users had to close the
drawer first and then switch projects/instances.

After this change, tapping a tab in that bar switches the
instance/project **and** the drawer auto-closes in a single gesture,
matching user expectation.

## Root cause

On phone (`max-width: 767px`), `InstanceShell` renders the left session
sidebar via MUI's `Drawer variant="temporary"`. The drawer paper is
offset down to `floatingTopPx()` so the instance tab bar remains
visually visible above it. However, MUI's `Modal` Backdrop is `position:
fixed; inset: 0` and covers the entire viewport — including the area
over the tab bar. The backdrop is styled `backgroundColor: transparent`
(so it's invisible) but it still captures pointer events at `z-index:
60`. Taps over the tab bar hit the transparent backdrop → MUI calls
`onClose` (closing the drawer) but the tab's click handler never fires.

## Implementation

1. **`packages/ui/src/components/instance/instance-shell2.tsx`** —
Constrain the MUI Drawer Backdrop via `sx` overrides on both the left
(session list) and right drawers so the backdrop is bound to the drawer
paper's vertical range (`top: floatingTopPx(); height:
floatingHeight();`) instead of fullscreen. Taps over the tab bar now
reach the tab buttons.

2. **`packages/ui/src/styles/panels/tabs.css`** — Lift
`.tab-bar-instance` to `position: relative; z-index: 70` so it stacks
deterministically above the drawer (z-index 60) across browsers as
defense-in-depth.

3. **`packages/ui/src/components/instance/shell/useDrawerChrome.ts` +
`instance-shell2.tsx`** — Expose `closeFloatingDrawersIfAny` from
`useDrawerChrome` and call it from an `InstanceShell` effect that fires
when `props.isActiveInstance` flips `true → false`. This closes any open
floating drawer on the instance the user just switched away from, so its
previously-open state doesn't bleed back when the user returns to that
tab later.

Tablet (>=768px) and desktop (>=1280px) layouts use pinned drawers (no
temporary modal), so the backdrop constraint and z-index lift are inert
there. The fix applies symmetrically to the right drawer on phone.

## Verification

- `tsc --noEmit` clean.
- `vite build` clean.
- Manual mobile-emulation checklist in the task file
(`tasks/done/058-mobile-session-list-blocks-tab-switch.md`) and SUMMARY
in `evidences/058-mobile-session-list-blocks-tab-switch/`.

## Reviewer manual check (phone viewport <=767px)

- Open session list drawer. Tap a different instance tab → it activates
AND drawer closes.
- Tap "+" while drawer open → folder picker opens, drawer closes.
- Tap Settings / Notifications / Remote while drawer open → action
fires, drawer closes.
- Same checks against the right drawer.
- Tap area below tab bar but outside drawer paper → drawer still closes
(existing backdrop dismissal preserved).
- Resize to tablet and desktop widths → pinned drawers unaffected.
- No visual regression in light or dark theme.

---------

Co-authored-by: Ubuntu <omer@Omer.dn3uxh3znnmu5eefnjnut0i1af.tlvx.internal.cloudapp.net>
Co-authored-by: Shantur Rathore <i@shantur.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

display thinking time, total response time

5 participants