Native desktop Chat
EXEPERT desktop provides one native Rust/GPUI Chat workspace with three isolated sources. EXEPERT uses the same authenticated Supabase and 9Router boundary as browser Chat. Codex is a visibly Experimental, version-pinned client for one owned loopback App Server and one optional remote WSS profile. DSH owns its separately authenticated bundled sidecar and local session catalog. The desktop does not embed a browser, Electron, React, or a WebView.
This page documents the shared native shell and the established EXEPERT Chat surface. The Codex process/protocol boundary, trust model, task lifecycle, and upgrade policy are recorded separately in Native Codex live client.
Change map
| Concern | Implementation |
|---|---|
| GPUI workspace and lifecycle | desktop/exepert-desktop/src/app.rs |
| Chat transport, authentication, persistence, image processing, and wire types | desktop/exepert-desktop/src/chat.rs |
| Chat visual primitives and context indicator | desktop/exepert-desktop/src/ui/chat.rs |
| Shared adaptive transcript reconciliation, scrolling, bookmarks, middle-button auto-scroll, and user checkpoints | desktop/exepert-desktop/src/ui/transcript.rs |
| Process-local Find, command-palette ranking, and unified attention reducers | desktop/exepert-desktop/src/desktop_discovery.rs |
| Strict machine-local navigation-density preference | desktop/exepert-desktop/src/desktop_ui_settings.rs |
| Native Chat virtual transcript projection | desktop/exepert-desktop/src/ui/native_chat_transcript.rs |
| Experimental Codex runtime, protocol, settings, reducer, and diagnostics | desktop/exepert-desktop/src/codex/ |
| Codex-specific native presentation primitives | desktop/exepert-desktop/src/ui/codex.rs |
| Pinned Codex 0.149.1 stable/experimental schemas and integrity manifests | desktop/exepert-desktop/schemas/codex-app-server/0.149.1/{stable,experimental}/ (with historical 0.125.0 evidence retained) |
| Native activity/action icons | desktop/exepert-desktop/src/ui/activity.rs and assets/activity/*.svg |
| Conversation Brain classifier, settings, evidence reducer, and activation seam | desktop/exepert-desktop/src/conversation_brain.rs and assets/brain/conversation-atlas-v1.json |
| Native image/runtime dependencies | desktop/Cargo.toml, desktop/exepert-desktop/Cargo.toml, and desktop/Cargo.lock (base64, getrandom, image with PNG/JPEG/WebP support, and sha2) |
| Shared browser-compatible request/catalog types | src/chat/client.ts and src/chat/models.ts |
| Edge Function registration and saved-prompt database types | supabase/config.toml and src/data/database.types.ts |
| Model discovery and explicit image capability | supabase/functions/_shared/nine-router.ts and chat-models/index.ts |
| Multimodal validation, routing, usage, and redaction | supabase/functions/run-chat/** |
| Authenticated prompt library | supabase/functions/chat-saved-prompts/index.ts and supabase/migrations/20260822173118_chat_saved_prompts.sql |
| Contract coverage | Rust tests under desktop/exepert-desktop/tests and focused Vitest files under src/__tests__ |
August 2026 native desktop change ledger
The August 28 desktop batch is one presentation, performance, and chrome upgrade layered on the existing Native Chat, Codex, collaboration, emoji, message-action, and Conversation Brain foundations. The table below is the review map for the code shipped by this batch; it intentionally records both user-visible behavior and the implementation seam that owns it.
| Change | Shipped behavior | Primary implementation |
|---|---|---|
| Shared Chat presentation | One persistent EXEPERT | ZED selector changes Native Chat and Codex visual hierarchy without changing models, protocols, drafts, tasks, or privacy. A compact menu replaces the tabs below 720 px. Missing, unknown, or malformed presentation values fall back only this preference to EXEPERT instead of discarding the rest of Codex settings. | desktop/exepert-desktop/src/app.rs, desktop/exepert-desktop/src/codex/settings.rs, desktop/exepert-desktop/assets/activity/chat-presentation.svg |
| Shared transcript performance | Native Chat and Codex use stable row identities, revision hashes, changed-range remeasurement, 512 px overdraw, conditional tail follow, unseen-content state, and per-chat or per-task bookmarks. Focus/copy/menu geometry participates in the matching Native row revision, and wheel or scrollbar interaction cancels pending tail follow. | desktop/exepert-desktop/src/ui/transcript.rs, desktop/exepert-desktop/src/ui/native_chat_transcript.rs, desktop/exepert-desktop/src/ui/codex_transcript.rs |
| Truthful streaming and disclosures | Native Chat, Codex, and DSH place a collapsed timing disclosure above agent output when trustworthy source timing exists. Worked is whole-response or whole-turn wall time; Thought is shown only for recorded provider-visible DSH reasoning spans. Codex continues to expose only authorized reasoning summaries and allowlisted structured developer details; raw reasoning and payloads remain excluded. Process-local disclosure and wrapping keys include source, owner, turn or message, item, and metric identity. | desktop/exepert-desktop/src/app.rs, desktop/exepert-desktop/src/response_timing.rs, desktop/exepert-desktop/src/ui/*_transcript.rs, desktop/exepert-desktop/src/{codex,dsh}/ |
| Message action geometry | User actions render in a sibling footer below the bubble. The assistant More menu measures its trigger, flips above or below, clamps to the live Chat stage, paints once at deferred priority 100, and closes on outside input or transcript scrolling. | desktop/exepert-desktop/src/app.rs, desktop/exepert-desktop/src/ui/chat.rs, desktop/exepert-desktop/tests/message_actions_ux_contract.rs |
| Confirmation measurement | A pending history mutation changes only its matching virtual-row revision. The consequence and equal-growth actions form a content-sized wrapping alert, preventing cached-height overlap with later messages. | desktop/exepert-desktop/src/app.rs, desktop/exepert-desktop/src/ui/native_chat_transcript.rs, desktop/exepert-desktop/tests/message_actions_ux_contract.rs |
| Inspector recovery | The rail action is idempotent open/restart, the shortcut remains a toggle, and Escape closes an active Inspector before Chat surfaces. Stale detached handles are reconciled before reopening. | desktop/exepert-desktop/src/app.rs, desktop/exepert-desktop/src/inspector.rs, desktop/exepert-desktop/tests/ui_contract.rs |
| Headerless desktop shell | The redundant 46 px workbench header is removed. A 36 px activity rail contains a separate four-mode tab list, while a 24 px full-width status rail owns passive state and interactive controls. | desktop/exepert-desktop/src/ui/workbench.rs, desktop/exepert-desktop/src/ui/chrome.rs, desktop/exepert-desktop/src/ui/theme.rs, desktop/exepert-desktop/assets/activity/mode-*.svg |
| Compile-time build details | Product version, desktop crate version, and a twelve-character Git commit are embedded at build time. Cargo watches Git's resolved HEAD and branch-ref paths in normal checkouts and worktrees. The sharp version badge opens its opaque details panel upward from the status rail and becomes static status text in benchmark mode. | desktop/exepert-desktop/build.rs, desktop/exepert-desktop/src/build_info.rs, desktop/exepert-desktop/src/app.rs |
| Collaboration and brand cleanup | Collaboration is a sharp 28×20 status action before the version control. The visible EXEPERT brand moves from the title bar to the first passive status slot, and ambient state renders one muted ACTIVITY / plus accented AMBIENT. | desktop/exepert-desktop/src/app.rs, desktop/exepert-desktop/src/ui/activity.rs, desktop/exepert-desktop/src/ui/chrome.rs |
| Third-party glyph provenance | The collaboration action uses Zed's byte-identical user_group.svg at commit d9ad6aff67e47de43abb270d22de75dd950f1b48. Both the repository GPL text and adjacent icon-directory Lucide ISC notice are preserved and embedded; their applicability to this glyph remains unresolved. | desktop/exepert-desktop/assets/third_party/zed/, THIRD_PARTY_NOTICES.md, desktop/exepert-desktop/src/ui/activity.rs |
| Source-selector identity | The Chat header uses original EXEPERT artwork plus locally embedded Codex and DeepSeek marks. The selected source reveals its label when the stage is wide; all marks remain visible in the icon-only narrow layout. Third-party source URLs, local checksums, adaptation notes, and release-review limits are recorded without claiming affiliation or license clearance. | desktop/exepert-desktop/assets/activity/source-exepert.svg, desktop/exepert-desktop/assets/third_party/thesvg/, THIRD_PARTY_NOTICES.md, desktop/exepert-desktop/src/ui/chat.rs |
The copied collaboration glyph is a release gate, not a completed licensing determination. Attribution and embedded notices do not establish compatibility with EXEPERT's MIT distribution. Public distribution requires legal review or replacement with an independently created, clearly compatible asset.
September 2026 transcript folding and navigation
The second transcript delivery extends the shared native boundary without changing protocols, settings, storage, paging, or server ownership. Codex commands, tools, developer activity, and authorized reasoning summaries now mount only a bounded one-line summary until explicit expansion. Native Chat, Codex, DSH Chat, and DSH Trajectory share browser-style vertical middle-button auto-scroll and independently virtualized checkpoint rails derived only from loaded user-authored rows. All disclosure, motion, marker, excerpt, and bookmark state remains process-local.
Checkpoint hover and keyboard focus share one compact native preview card. The
loaded user query is bright and bounded; a matching resident final-assistant
answer appears beneath it in a quieter treatment. Native Chat pairs within the
next user boundary, Codex accepts only AgentFinal from the same turn, and DSH
prefers the final assistant row with the same turn identity. Missing resident
answers are omitted rather than inferred. Card width is clamped to the live
viewport and both excerpts are Unicode-safe, wrapped, and line-clamped.
September 2026 native discovery and adaptive navigation
The third desktop delivery replaces Codex's transcript-pushing search strip
with one shared native Find in Chat overlay. Ctrl+F or the compact search
icon in the active source header opens it without changing transcript layout.
Typing searches currently resident EXEPERT and DSH user/assistant rows
immediately. Codex searches those same visible message classes locally, then
debounces the version-pinned thread/searchOccurrences request. Commands,
developer messages, tools, DSH trajectory activity, and hidden reasoning are
excluded. Server, task, selection epoch, query revision, and search generation
qualify remote responses; stale results cannot replace the current query.
The first 50 Codex occurrences are loaded initially. A count ending in +
means the server owns another page. Moving forward from the final loaded match
requests that page and selects its first validated result; wrapping begins only
after the server reports that paging is exhausted. An unloaded result retains
the established inclusive-turn hydration and exact turn/item validation before
showing a bounded focused view. The active match has a stronger transcript-row
marker and its compact preview highlights the matched Unicode text; other
loaded matching rows keep a quieter marker. Enter moves forward,
Shift+Enter moves backward, and Escape closes Find and restores the exact
previous focus.
Ctrl+K or titlebar-global-search opens the native global command palette.
Its independently virtualized groups are Recent chats, Codex tasks,
DSH sessions, Quick actions, Navigation, Panels, and
Settings. Exact and prefix matches rank before substrings within each group,
with recency breaking ties. Codex uses thread/search and DSH uses its session
search contract; provider responses remain query-qualified and Codex exposes an
explicit next-page control. The palette searches titles, IDs, workspace paths,
catalog metadata, and commands only. It never indexes, displays, persists, or
logs transcript bodies. Arrow keys, Home, End, Enter, and Escape provide the
keyboard path. Find in current chat transfers directly to the Ctrl+F
surface.
The title bar's non-drag left cluster is ordered as navigation density, global
search, and attention. The native drag region follows, then icon-only System
Status and Build utilities, and finally the Windows controls. Both utility
clusters remain outside the drag region.
Ctrl+B and titlebar-navigation-density share the exact cycle:
- Full — activity rail and source/sidebar panel are mounted.
- Sidebar only — the source/sidebar remains and the activity rail is not mounted.
- Focus — neither navigation region is mounted.
The accessible name reports both the current and next state. Focus moves to the
active stage before a navigation owner disappears, and popovers anchored to a
disappearing region close. Only navigation_density is written to the strict,
versioned %LOCALAPPDATA%\EXEPERT\desktop-ui-settings.json; malformed,
oversized, unknown-field, or future-version content safely defaults to Full.
Benchmark mode always renders Full and never writes this preference.
September 2026 seamless workbench polish
Navigation and the main title bar share SHELL_SURFACE (#181818), including
the idle backgrounds and borders of the 30 px title-bar actions. Their selected,
hover, pressed, and keyboard-focus feedback remains distinct. The activity
rail has no right border.
The September 3 title-bar follow-up makes the stable workspace-inset meet
the bottom of the 34 px title bar and fill the client area's right and bottom
edges. The stage and context panel share WORKSPACE_SURFACE (#121214) and
are clipped to an 8 px top-left corner with 1 px subtle top/left borders. The
other corners are square; there is no outer shadow or top/right/bottom gutter.
The left edge stays flush with visible navigation and gains an 8 px gutter
only in Focus mode. The shared WORKSPACE_GUTTER token remains 8 px for that
left gutter and independent popover margins. The context panel keeps a single
internal left divider when side by side and a top divider when stacked.
The responsive side-by-side thresholds are 1,189 logical pixels in Full,
1,153 in Sidebar Only, and 937 in Focus, accounting for the single left border.
Brain telemetry remains 84 logical pixels tall at the bottom of its center
column; a stacked context panel follows below it. All caption controls use
native drawings centered in their actual hit areas, inheriting idle and hover
foreground colors independently of font metrics. Nominal 1 px strokes and
10 px icon footprints are rounded consistently in device pixels. Minimize is
a 10 by 1 line; maximize is an outlined 10 by 10 square; close is a symmetric
cross. When window.is_maximized() is true, maximize becomes two overlapping
8 by 8 restore squares, offset by 2 px within the same footprint. The main
window retains its 42 by 34 caption hit areas, and the detached Inspector and
Runtime Log reuse the same helper. Native drag, minimize, maximize/restore,
and close behavior continues through GPUI's WindowControlArea.
Navigation retains the exact Full -> Sidebar only -> Focus -> Full cycle.
Geometry uses a 170 ms ease-out-quint transition, while three original local
glyph layers crossfade and scale over 140 ms. Rapid Ctrl+B or pointer input
retargets from the currently painted widths; generation checks stop stale
frames. The responsive side-by-side decision uses the final target widths, so
the breakpoint cannot oscillate during motion. Reduced motion applies the final geometry
and glyph immediately, including when the preference changes during an active
transition, and deterministic benchmark mode remains Full without changing the
saved preference.
Before a navigation region begins disappearing, focus moves to the active
stage and anchored popovers close; open Find, Global Search, and Attention
overlays keep focus on their own input or panel. The active transcript captures
its stable first-visible row, nonzero within-row offset, scope, and interaction
revision before the width change. The transcript list holds one measured width
during the 170 ms shell motion, then releases it for one final-width reflow.
After settlement, restoration proceeds only if no later wheel,
checkpoint, jump, auto-scroll, tail-follow, or scope/surface change superseded
it. Native scrollbar movement participates through a shared content-free drag
revision, and successful restoration cancels active motion. This bookmark, the
transition, glyph opacity, and tooltip state are all process-local and never
serialized or logged. Only navigation_density continues to enter the strict
desktop UI settings file.
The 30 px title-bar actions use one cached structured native tooltip for hover
and keyboard focus. Escape dismisses it until both hover and focus leave, and
shortcut pills are mirrored into native accessibility shortcut metadata. The
navigation copy follows its next action: Hide activity rail,
Enter Focus mode, or Restore navigation, each with a Ctrl+B shortcut
pill. Global Search shows Search and commands with Ctrl+K; Attention does
not advertise an invented shortcut. The accessible navigation name still
reports the current and next state. Settled zero-width navigation is unmounted,
so refreshed UI Automation output has no hidden rail or sidebar descendants.
Stable IDs added or retained for this shell include workspace-inset,
navigation-transition-wrapper, navigation-glyph-transition,
navigation-glyph-full, navigation-glyph-sidebar-only,
navigation-glyph-focus, titlebar-action-tooltip, and
titlebar-action-tooltip-shortcut.
titlebar-attention aggregates process-local, bounded references to pending
Codex/DSH approvals, authoritative Codex and collaboration unread counts,
updates to inactive EXEPERT/DSH destinations, and recoverable runtime or
transport failures. It stores stable IDs, source/kind metadata, a bounded label,
and a navigation action—not transcript snippets, request payloads, credentials,
or approval authority. Items are deduplicated and ordered Needs action,
Unread, then Failures; the badge caps at 99+. Opening unread content
acknowledges it. Approval items remain until the source resolves them, and
selection never approves a request. Failures remain until recovery or explicit
process-local dismissal, and a changed or recurring failure resurfaces.
Find, global search, and attention are mutually exclusive top layers. Escape
closes only the active top layer. Their stable accessibility roots are
find-in-chat, global-search-dialog, and attention-panel; title-bar roots
are titlebar-navigation-density, titlebar-global-search, and
titlebar-attention. Result, retry, empty, group, option, badge, and source-
qualified toggle IDs are stable descendants. Both virtualized discovery lists
retain the existing fewer-than-80 realized-row gate.
September 2026 unified workspace and rail collaboration
The fifth native desktop delivery removed the 24 px bottom status bar. The
current workspace also uses the former bottom gutter, as described above.
The root, title bar, activity rail, and navigation sidebar use
SHELL_SURFACE (#181818). The non-painting workspace-inset-gutter owns the
remaining Focus-mode left spacing. The clipped workspace-inset, its center
stages, Chat-source stage shells, composer surroundings, and right context
panel use WORKSPACE_SURFACE (#121214). Cards, inputs, dividers, and other
elevated surfaces keep their existing tokens.
The left title-bar order is Navigation, Search, Attention, System Status, then
Build. System Status and Build retain their 30 px icon-only buttons, existing
2 px gaps, and stable titlebar-utility-controls toolbar, now nested after
Attention inside titlebar-left-controls. The remaining title-bar space is
draggable; the main renderer supplies no trailing utilities. System
Status opens the bounded desktop-status-panel, which contains the retained
desktop-runtime-status product label and truthful desktop-preset-status
activity or preset value. Build retains desktop-version-badge, its compile-
time product/crate/commit details, and keyboard focus restoration, but no longer
renders a textual badge. Both panels open below the title bar with an 8 px
offset and use Anchor::TopLeft, with the existing 8 px window-edge
containment. Benchmark mode uses the same no-status-bar geometry and omits
these interactive utilities.
The focused toolbar owns Tab and Shift+Tab traversal in visual order. Its
EXEPERT_TITLEBAR key context excludes Brain bindings so Enter/Space reach the
existing title-bar activation handlers. Brain preset keyboard behavior remains
available when focus is outside the toolbar.
Human Collaboration is the first item in activity-global-controls, above the
Inspector when available and Settings. collaboration-popup-trigger is a
native 28 px popup action with truthful aria-expanded, connected/open styling,
focus-visible treatment, a dynamic tooltip and accessible name, and the retained
collaboration-unread-badge. The action is mounted only when the collaboration
runtime and Full-density rail are available. Ctrl+K exposes the stable
global-search-option-quick-open-collaboration quick action in every normal
density, and Attention routes collaboration items through the same opener.
In Full density the collaboration panel begins at rail width plus the 8 px workspace gutter. In Sidebar Only and Focus it begins 8 px from the window's left edge. Width, height, and left placement clamp to an 8 px viewport margin, and the panel bottom aligns with the workspace gutter. Opening provenance stays process-local: a rail-origin close restores the mounted trigger, while palette, Attention, guarded remote-control, and message-sharing opens restore the active stage. Cycling away from Full closes a rail popup and transfers focus before navigation motion, so an unmounted rail leaves no hidden UI Automation node.
Find, Global Search, Attention, Human Collaboration, System Status, and Build share one top-level exclusion policy. Opening one closes a conflicting surface; Escape dismisses only the active top layer before returning focus. No new UI preference is serialized: only the existing navigation density preference and existing collaboration preference behavior remain.
September 2026 native response timing and row alignment
All three native Chat sources now use the same typed timing presentation above agent output. A live measurement reads Working for … and freezes as Worked for …. An exact failed or cancelled measurement reads Stopped after …. A second Thinking for … or Thought for … row is present only when DSH records trustworthy provider-visible reasoning boundaries. Missing, malformed, partially paged, or backwards timing data is omitted instead of estimated.
The source rules remain deliberately different:
- EXEPERT starts a process-local monotonic clock at
ChatEvent::Startedand replaces it with persisted backendtotal_latency_mson successful completion. This is request latency, not private thinking time, so EXEPERT does not synthesize a Thought row. - Codex retains per-turn
startedAt,completedAt, anddurationMs. An explicitdurationMswins; otherwise a valid timestamp difference is used. A local monotonic anchor drives only the provisional live display. Boundedthread/turns/listhydration supplements resident historical turns without changing transcript ownership or the established item/turn/read fallback. Codex 0.149.1 exposes no reasoning duration, so its existing safe reasoning summary remains separate and no Thought timer is inferred. - DSH prefers an explicit recorded turn duration, otherwise a matching
turn/startto terminal source-timestamp difference. Thought sums merged recorded reasoning block/delta spans across model steps and excludes tool or approval gaps. The already-redacted provider-visible text moves into the bounded Thought card; consumed reasoning rows are hidden only from Chat and remain unchanged in canonical Trajectory.
Each metric is a full-width button with a stable source-qualified ID, native focus styling, keyboard activation, and truthful expanded state. Disclosures start collapsed. Height, opacity, and chevron direction retarget smoothly over 150 ms; reduced-motion mode applies the final state immediately. Expanded content is capped at 360 px with internal scrolling, timing facts are bounded, and reasoning content is capped before rendering. One visible-surface 1 Hz repaint ticker updates running labels without changing row identity or forcing measurement on every tick.
ZED response geometry now routes the active streaming row through the same centered 760 px assistant shell as completed output, including its 20 px content inset. The 720 px response footer is centered inside that shell. Standard EXEPERT presentation retains its existing 720 px alignment. Switching presentation invalidates affected virtual-row measurements while stable keys, bookmarks, and tail-follow preserve the reading position.
Chat sources and isolation
The existing 42 px Chat header exposes accessible EXEPERT, CODEX, and
DSH tabs in a native TabList. Each source keeps a locally embedded icon;
the selected tab also shows its text label at 720 px or wider, while narrow
stages retain icon-only tabs with tooltips and unchanged ARIA names. Codex
remains visible when its executable is missing or incompatible and carries an
Experimental status label. The shared header dispatches presentation only;
it does not merge source state.
EXEPERT retains its NativeChatState, Supabase transport, verified model
catalog, image processing, feedback, brain grounding, saved prompts, focus,
virtual transcript state, and transcript persistence. Codex owns a different
textarea, server-scoped attachment list, task reducer, model/effort choice,
approvals, focus, virtual transcript state, diagnostics, an authenticated owned
loopback WebSocket runtime, and an optional remote WSS runtime. Switching tabs
or tasks preserves those process-local values independently. Codex prompts,
responses, approvals, attachment contents, tokens, and protocol payloads never
enter ChatPersistence, Supabase, EXEPERT feedback, or affect telemetry.
Only non-content Codex selections are saved: the exact executable path, up to eight attached task IDs, active task, last workspace, active Chat source, and UI preferences. The runtime starts lazily only when Codex Chat is selected; renderer benchmark and probe modes never initialize it. See the architecture record for the exact 0.149.1 protocol, persisted-read firewall, raw-reasoning exclusion, Windows Job ownership, recovery sequence, and no-spend validation boundary.
Presentation selector and shared transcript
The Chat header includes one global EXEPERT | ZED presentation preference.
It is stored as CodexUiPreferences.chat_presentation; a missing field defaults
to EXEPERT without changing settings schema version 2. The stable controls
are chat-presentation-tabs, chat-presentation-exepert, and
chat-presentation-zed. Below 720 px of Chat-stage width they collapse into
chat-presentation-menu-trigger and an opaque two-option popup.
The selector changes visual hierarchy only. A switch preserves the current Native chat or Codex task, draft, attachments, running request, speech, collaboration, and logical reading position. Message-anchored and composer popups close because their geometry is no longer valid, then only affected rows are remeasured. No model, protocol, privacy, persistence, or network behavior changes.
Native Chat and Codex share an adaptive variable-height ListState engine with
stable row keys and revision hashes, prefix/suffix splicing, changed-range
remeasurement, 512 px overdraw, a 48 px near-bottom threshold, and a bounded
140 ms glide for non-precision wheel input. Precision input and reduced-motion
input remain direct. Bookmarks are scoped by ChatId or CodexTaskKey, so
switching conversations restores the prior logical reading position instead of
blindly jumping to the newest message.
The same scroll boundary supplies vertical middle-button auto-scroll to Native Chat, Codex, DSH Chat, and DSH Trajectory. An unmodified middle click arms a visible anchor; pointer movement inside a 12 px vertical dead zone does nothing, then speed grows linearly to a 1,800 px/s cap. Frame time is clamped to 50 ms. Reaching an edge pauses movement without disarming the anchor, so moving back across it resumes in the opposite direction. A second middle click, another pointer button, wheel input, Escape, an explicit jump, or a transcript scope change cancels it. Starting auto-scroll pauses tail follow. Reduced-motion mode keeps the functional indicator but omits decorative motion.
When loaded user-authored rows exist, each of those surfaces also renders a
compact independent virtual checkpoint rail. Marker identity comes from the
stable transcript row key; the accessible label is Jump to user message N
and its compact hover/focus card shows the user excerpt in primary text plus an
optional resident final-assistant excerpt in secondary text. Each excerpt is
normalized to one line and capped independently at 160 Unicode characters;
the same paired copy is available in the accessibility description. Native
Chat pairs the last assistant message before the next user turn, Codex pairs
only an authoritative same-turn final answer, and DSH pairs only a completed
same-turn assistant/message record—not stream chunks or model-boundary rows.
The ordinal describes only the currently loaded chronological window. Click,
Enter, or Space jumps through the existing stable-key path; Up, Down, Home,
and End navigate markers. The active marker is the latest loaded user row at
or above the first visible transcript row. The rail never requests older data.
When a Codex resident page has no user row and an older page can actually be
loaded, a visually distinct unnumbered rail affordance jumps to the existing
Load Older History row. It is omitted at the 500-item cap and on reduced-
fidelity servers without older paging. It never calls the runtime or initiates
paging; loading remains an explicit action on that row. Adding or removing a
rail remeasures resident transcript rows while the stable-key bookmark keeps
the reading anchor.
Native response events continue to drain in 40 ms batches. In ZED presentation, the first visible answer token creates one process-local provisional assistant row; before that token, the stable timing header shows Working for …. The provisional buffer is UTF-8 safe and bounded to 512 KiB. Completion atomically replaces it with the authoritative stored response, while cancellation or failure removes it and retains the existing retry flow. Provisional content is never persisted, sent back as model history, or represented as reasoning.
EXEPERT presentation retains the established cards and disclosures. ZED uses
the same optimized engine with compact 8 px user cards, flat assistant output
in a centered 760 px reading column, a 20 px response inset, a centered 720 px
response footer, quiet timeline guides, borderless message actions, and a 12 px
shadowless composer. Codex Thinking may contain only the
App Server's authorized ReasoningSummary; it and all other expandable
activity begin as a one-line summary. The full projected text and developer
Structured details are not mounted until explicit expansion, and collapse
unmounts them again. Details come only from the reducer's bounded allowlisted
item.details. Raw reasoning,
protocol payloads, prompts, credentials, and encrypted fields stay excluded.
There is no bottom status rail. Human Collaboration lives in the activity rail, with Ctrl+K and Attention fallbacks when the rail is hidden. System Status and Build use progressive disclosure in the left title-bar utilities. Closing the collaboration surface never ends an active room, and benchmark mode omits all three interactive utilities without creating dead tab stops.
User experience
Compact composer
The Chat composer is content-driven instead of using a fixed input-strip
height. Its shell has a 12 px horizontal inset, no top divider, and an 8 px
bottom gap that gives the inner surface visible separation from the window
edge. The inner surface uses 12 px horizontal padding, 8 px vertical padding,
an 8 px content gap, a subtle outline and shadow, and a Chat-local 20 px radius;
the rest of the desktop retains EXEPERT's 2 px visual system. Replacing the
former 12 px top-and-bottom inner padding with 8 px on each side recovers the
same 8 px used by the shell gap, so normal composer chrome remains 24 px tall
and the total footprint does not grow. The outline changes to ACCENT_DIM
while the textarea has focus. The textarea has a 40 px minimum height, submits
with Enter, inserts a newline with Shift+Enter, and grows with visually wrapped
typed or pasted content. Its responsive maximum is 8 rows at logical viewport
heights through 720 px, 10 rows from 721–899 px, and 12 rows from 900 px upward;
after that cap the textarea scrolls internally. The drag/drop overlay follows
the same 20 px composer boundary.
After the first user turn, a normal text-only composer remains approximately 96–112 px tall. It expands only for multiline text, attachment cards, inline notices, or the initial suggestion row. The row buckets are a soft 45% target for text-dominant states; required auxiliary surfaces may temporarily add their own bounded height.
The latest viewport bucket is process-local. EXEPERT applies it immediately
while the textarea is empty, including initialization, New Chat, successful
Send, manual clearing, and empty Chat re-entry. While text is present the
currently applied cap is frozen and the latest bucket remains pending. This is
intentional: the pinned GPUI textarea resets its measured row mode when the cap
is replaced, so deferral preserves the draft's geometry, selection, undo
history, and focus through window resizing. GPUI continues to remeasure normal
text edits and width-driven wrapping within the active cap. Programmatic
composer replacements from prompt history, Saved prompts, and quick
suggestions explicitly enter the same responsive reflow path because the
pinned textarea intentionally suppresses change events for set_value.
The bottom row keeps these persistent actions:
- a 32×32 Tools and attachments trigger at the left;
- flexible space;
- a compact verified-model selector immediately before the primary action; and
- Send or Stop in one stable 36 px slot.
The Tools menu contains only implemented actions: Add photos & files and Saved prompts. Both are direct native menu items rather than a placeholder tool hierarchy. Existing image limits and saved-prompt authentication still control each item's disabled state and explanation.
The three quick suggestions render only before the first user message. A text turn or an image-only turn hides them. New Chat clears the session and makes them available again, while the Prompts library remains available throughout.
Tools and model popovers
The plus control is exposed as Tools and attachments and opens GPUI's native
PopupMenu, so assistive technology receives Menu and MenuItem semantics
and keyboard users can open the trigger with Enter or Space, then use arrow
keys, Enter, and Escape. EXEPERT uses the component's standard labelled rows—the
only pinned API that gives the outer selected MenuItem a reliable Windows UIA
name. The stable chat-attach-image and chat-prompts-trigger IDs remain as
zero-size inspector aliases inside the menu surface because this pinned
PopupMenu has no custom row-ID hook; the aliases add no second accessibility
role or hit target. When one action is unavailable, its reason is rendered as
native menu copy and included in the Tools trigger description. Because the
pinned native menu initially selects row zero, EXEPERT keeps the first keyboard
row actionable whenever only one tool remains available and independently
guards both action handlers. An open menu is rebuilt when processing, pending,
or cancellation state changes, so its availability and copy cannot become
stale; if both actions become unavailable, the menu closes instead of remaining
logically expanded behind a disabled trigger.
Escape returns focus to the plus trigger. Pointer dismissal does not explicitly
move focus, adding or removing images restores the textarea, and selecting
Saved prompts moves focus into its search field. Tools, the model catalog,
and the saved-prompt library are mutually exclusive and all close on New Chat
or when leaving Chat. If both menu actions are unavailable, the non-focusable
trigger tooltip includes both reasons rather than hiding them.
The selected-model control is 32 px high, uses a 120 px minimum and 240 px
maximum width, and truncates long names to one line. Its accessible name and
tooltip retain the full model, provider, and context state. The 16 px provider
logo is supplemental: Claude/Anthropic entries use claude.svg,
OpenAI/GPT/Codex entries use openai.svg, and Gemini, combo, Mimo, or unknown
families use generic.svg until a dedicated asset is supplied. Picker rows
continue to show provider text, so identity never depends on the SVG alone.
The model catalog opens above and right-aligned to the compact trigger with an 8 px gap. Its approximately 400 px independent width is bounded to the Chat surface. Model search, keyboard navigation, explicit selection, capability labels, and routing behavior are unchanged. The model and saved-prompt surfaces prefer conservative 400 px and 360 px maximum heights respectively, then clamp those heights to the live space between the Chat header and composer trigger. Their list bodies flex-shrink and scroll independently, so the whole popup stays inside the required compact viewport instead of merely clipping its list.
Models, Saved prompts, and Tools share an opaque Chat-local popup treatment:
solid SURFACE_2, BORDER_STRONG, a restrained 10 px radius, inner highlight,
and shadow. The two custom library surfaces defer their paint at priority 100
so they cover transcript and composer content. Tools keeps GPUI's native
Popover host and its single native priority-100 deferred layer; EXEPERT does
not wrap that menu in a second deferred surface or replace its Menu/MenuItem
semantics. The pinned PopupMenu also paints its own opaque nested fill and
offers no per-instance background override, so EXEPERT aligns the component
theme's process-wide popover background token to SURFACE_2 at startup. The
Chat shell still owns the strong outline, 10 px radius, inner highlight, and
shadow; no menu geometry or semantics change. The former whole-panel opacity
animation remains removed.
This is a frosted-glass visual suggestion, not real backdrop blur: the pinned
GPUI revision has no backdrop-filter API, and EXEPERT adds no transparency,
platform-composition hook, dependency upgrade, or custom blur render pass.
GPUI's .occlude() controls the popup pointer hitbox; the fully opaque fill and
deferred paint order—not occlusion—provide visual coverage.
Transcript hierarchy
Assistant responses are flat transcript content: they have no outer card background, border, radius, or padding. The visible EXEPERT label remains, with a 6 px gap before its response body. Feedback and response metadata align to the same left edge as that body.
User messages are compact, right-aligned SURFACE_3 bubbles with 12 px padding,
a subtle outline, and a Chat-local 16 px radius. The visual YOU label is
removed, while the semantic User message identity remains available to
assistive technology. Attachments, failed-response panels, and other actionable
or error surfaces keep explicit boundaries.
Conversation history and message actions
EXEPERT Chat stores a versioned set of conversations instead of overwriting one global transcript. Each chat and message has a stable ID, timestamps, its own session key, and bounded safe response metadata. The store retains at most 20 chats, 50 messages per chat, and 8 MiB total. It prunes the oldest inactive chat when needed and reports that event. The active chat, the conversation just left during a switch or New Chat, and both sides of a new fork are protected from size pruning; if that protected set cannot fit, it remains memory-only instead of silently deleting user work. Legacy message-array state migrates deterministically into one active chat. Atomic replacement preserves the existing rule that attachment bytes never enter the saved transcript.
The recent-chat selector switches between retained conversations. New Chat preserves the current conversation and reuses an existing blank chat instead of accumulating duplicates. Fork creates an independent session containing history through the selected message. Switching and branching are unavailable while a request mutation or response edit is pending, and switching stops speech and closes message popups.
Completed Native Chat messages expose role-appropriate 32 px contextual actions. Assistant responses support Copy, local Edit, Regenerate, confirmed Delete, Read aloud, Fork, Markdown export, and reported response details. User messages support Copy, Edit and resend, Fork, and confirmed Delete. The newest assistant action row stays visible; older and user rows remain mounted for pointer and keyboard reveal. Request-backed edit/resend and regeneration stage their replacement and retain the original transcript until the replacement succeeds. Mutation actions are disabled while sending or cancelling, while Copy, export, details, and speech remain available. An image-backed prompt whose process-local image bytes are no longer retained cannot be edited or regenerated; the action explains that the image must be attached again.
Read aloud uses the default Windows system voice from one bounded SAPI worker. Starting another utterance stops the prior one; message removal, chat switching, explicit Stop, and shutdown also stop playback. Only visible plain text reaches the worker. Reasoning, tool payloads, credentials, attachments, and speech content are excluded from logs.
Response details open in a non-modal opaque right-side sheet with a visual scrim, Escape handling, and focus restoration. The sheet shows only values reported by the backend, including safe model, provider, identifiers, timing, usage, and context fields. First-token speed, generation speed, chunks, and tool metrics are marked unavailable when the response does not report them; EXEPERT does not infer them. Missing fallback flags remain unavailable, and estimated context counts are explicitly labelled as server estimates.
Conversation Brain
Both EXEPERT and Codex Chat place an Experimental two-brain monitor at the top of the right panel. It distinguishes HUMAN · PREDICTED ANALOGY from AI · INTERFACE ACTIVITY, labels observed versus inferred evidence, and shows submit/response direction without claiming mind-reading. Selecting the card opens the full BRAIN CONNECTION view; the original SIMULATION view remains separate.
Semantic inference is a bounded, offline English/Malay visible-text heuristic. Empathy mode is opt-in, analyzes an idle draft locally, and exposes its fixed tone/depth suggestion for user override before Send. Native Chat transmits only validated enums that the Edge Function maps to trusted presentation copy; Codex adds a separate visible cue only to a new turn. Derived activity and overrides are process-memory only. See Conversation Brain for the evidence, privacy, atlas, and future activation-provider boundaries.
Header, keyboard, and accessibility
The existing 42 px header now uses icon-first EXEPERT, CODEX, and DSH
source tabs while retaining its bottom divider and Close dimensions. The
stable IDs remain chat-source-exepert, chat-source-codex, and
chat-source-dsh; hover and keyboard focus expose the source name, while the
selected source also shows its text label at header widths of 720 px or more.
Enter and Space activate a focused inactive tab. EXEPERT
keeps the stable chat-new-center action; Codex exposes codex-new-task and
requires a native workspace-folder selection. Header controls are transparent
at rest, use SURFACE_3 on hover, and show a keyboard-only accent focus ring
without changing layout.
While Chat is active, Tab and Shift+Tab use GPUI's native focus traversal. Renderer-preset traversal remains unchanged outside Chat. Every enabled Chat action exposes a focus-visible ring; disabled actions remain outside the tab order.
The native accessibility tree exposes the transcript as a Log, each message
as an Article named User message or EXEPERT response, ordinary
asynchronous copy as Status, and failures as Alert. Meaningful small labels,
notices, save state, feedback text, and response metadata use TEXT_SECONDARY;
muted and disabled tones are reserved for redundant or unavailable content.
Dynamic response, setup, composer, attachment, feedback, save, and error copy is
also included in native accessibility labels instead of relying on visually
rendered text alone. Saved-prompt rows are semantic groups with explicit
keyboard-accessible Use, Rename, and Delete actions, so interactive
controls are never nested inside another focusable action.
Smart response following
The transcript treats content within 48 px of the bottom as already following the conversation. Sending always reveals the new user turn. A completed or failed response auto-scrolls only if the transcript was already within that threshold. Whenever a non-empty transcript is outside that threshold, a keyboard-accessible 32 px circular Scroll to bottom arrow floats 12 px above the transcript's bottom edge without consuming layout height. Its 16 px chevron remains flex-centered, and the existing 140 ms appearance motion grows the control from 28 px to 32 px; reduced-motion mode resolves directly to the final size. The same geometry is used by EXEPERT, Codex, and DSH Chat. An offset accent dot appears only when new content arrived below the current viewport, without covering the compact chevron.
The send path records a short-lived tail-follow intent before the new row is painted, preventing a fast response from racing the first scroll layout. The same guarded intent keeps the transcript bottom-aligned when textarea growth or window reflow begins while the user is already near the bottom. Each intent is generation-qualified so an older delayed layout callback cannot settle a newer send, resize, or input request. Manual wheel interaction or a completed scrollbar interaction cancels that intent. Activating Scroll to bottom, returning near the bottom through those observed scroll paths, a reconciled window resize, sending another turn, or starting a New Chat clears the unseen response state. If bounded transcript retention removes old leading rows while the user is reading history, GPUI compensates the offset by the removed row height. Automatic response updates never move focus away from the textarea.
Contextual action placement
- New Chat is in the Chat header immediately before Close.
- Retry appears only in the failed-response panel in the transcript.
- “Was this response useful?”, Yes/No, structured/model metadata, and local save status render beneath only the latest assistant response.
- Feedback choices are both disabled while saving. A successful selection keeps its accent state. A failed save shows an inline error and re-enables both choices.
The inspector-facing IDs remain stable, including brain-stage,
chat-transcript, chat-composer-shell, chat-composer, chat-send,
chat-stop, chat-new-center, chat-close, chat-message-*, feedback,
Retry, prompt, model, and attachment IDs. The conditional response-following
control uses the internal ID chat-scroll-to-bottom; the Tools trigger adds
chat-tools-trigger. Existing chat-prompts-trigger, chat-attach-image,
chat-model-anchor, chat-model-trigger, and chat-model-picker IDs remain
stable in their new hierarchy.
Codex adds source-local IDs prefixed with codex-, including
codex-task-panel, codex-transcript, codex-message-*,
codex-composer-shell, codex-composer, codex-send, codex-steer,
codex-stop, approval controls, optimistic submission Retry/Discard controls,
sanitized diagnostics, and read-only mirror controls. Mirror keeps stable
codex-mirror-task-*, codex-stop-mirror, codex-mirror-status,
codex-mirror-read-only, and codex-mirror-copy identities. These additions
do not rename any EXEPERT inspector contract.
Offline emoji and local reactions
Native Chat and writable Codex composers share one opaque, responsive emoji
picker. Its compact catalog is generated at release time from pinned
emojibase-data 16.0.2 English and Malay metadata and embedded in the desktop
binary; the generator verifies the package version and both input checksums
before stamping that provenance. Search, category labels, recents, and
skin-tone variants therefore work without a CDN, telemetry call, or runtime
network request. Left/Right/Home/End remain native caret keys in search; Down
moves into a single-focus results grid, whose accessible label follows the
highlighted item. Category tabs use roving focus, and Tab/Shift+Tab remain
contained inside the open dialog. GPUI inserts the selected Unicode text
through the textarea's atomic replace operation, so the current caret,
selection, and undo history remain authoritative. Windows draws the characters
with its native emoji font; EXEPERT ships no third-party emoji artwork.
Completed Native assistant responses accept up to six unique local emoji reactions. Each reaction is at most 64 UTF-8 bytes and is saved through the existing atomic version-2, 8 MiB chat store. Forks copy them, local response editing clears them, and truncation removes them with the affected messages. They never enter model context, exports, feedback, telemetry, or collaboration traffic. User messages and non-response rows do not expose reaction controls. The picker keeps at most 24 recent choices and a preferred skin tone only for the current process. The preferred tone is applied consistently to compatible catalog entries, recents, and default quick reactions.
Experimental Codex task experience
The Codex left panel contains only explicitly attached tasks plus a separate stored-task browser. Each attached row reports title, workspace, activity or approval state, and unread count. Takeover is a two-step confirmation that the task is idle in every other Codex client; Fork remains the safe alternative. Detaching an idle task unsubscribes without deleting history. An active task uses a separate Interrupt + Detach confirmation and fails closed if the runtime has not supplied its turn ID. Pending approval in an inactive task also creates a persistent alert above the task list.
Stored-task discovery polls complete recency-sorted pages every second while Codex is foregrounded and every five seconds in the background. It explicitly includes CLI, VS Code, and App Server sources and replaces rows only after the whole epoch succeeds. A failure retains the previous rows and backs off for 30 seconds; Refresh starts a new epoch without clearing the panel. Recovery scopes epochs to the replacement App Server generation, so a prior high epoch cannot hide fresh rows.
Completed discovery pages and normalized mirror presentation snapshots receive
process-local SHA-256 fingerprints. After the initial result, an identical
poll is accepted for freshness and scheduling but emits no duplicate UI event.
Recovery from STALE or UNAVAILABLE is always emitted even when the restored
transcript content is identical. Reducer applications classify accepted
changes as no-op, metadata-only, or transcript-changing. A no-op notifies
nothing; metadata refreshes the workbench; transcript changes reconcile the
child and perform one root refresh for shared task/event metadata. Manual
Refresh still performs a full network reconciliation; an identical answer
simply causes no repaint. Poll cadence, privacy limits, and stale-epoch
rejection are unchanged.
Each stored row offers Mirror, Take Over, and Fork. Mirror is one
ephemeral, read-only persisted view outside the eight attached tasks. It shows
SYNCING, MIRRORING, STALE, or UNAVAILABLE, exposes Copy and Stop Mirror,
and replaces the interactive composer. It cannot send turns, approvals,
interrupts, attachments, or model changes. Full item pagination falls back to
turn pages and then a capped five-second thread read with an explicit reduced-
fidelity warning. Mirror selection and content never enter settings. Take Over
or Fork keeps the mirror until its matching acknowledgement succeeds. Mirror
start/stop uses a lossless last-write-wins lifecycle directive, so Stop also
wins while the bounded mutation queue is full or App Server is recovering.
Incomplete turn item views and malformed item identity never appear as a current
snapshot; large update bursts reload the newest bounded 500-item window.
For controlled tasks, Task creation, Fork, Takeover, and Detach save the
outgoing draft and scroll before selecting the next task, then restore the
incoming task without moving content between tasks. A mirror has no draft to
save and is cleared from memory on Stop, switch, exit, or recovery.
Takeover does not select its task until thread/resume succeeds. Active detach
waits for a terminal turn after interrupt before unsubscribing, and both idle
and active detach retain the row until thread/unsubscribe succeeds. A queue,
timeout, or server failure cancels only the transient operation and preserves
the existing task state. Pending creation, Fork, and Take Over actions reserve
one of the eight controlled slots, with a second runtime-side capacity check
before any acknowledgement is accepted.
Each task retains an independent process-local draft, remote-image draft, local
and remote attachments, model, supported reasoning effort, transcript scroll,
and pending submission. The Codex textarea follows the same responsive
8/10/12-row viewport buckets and populated-draft resize freeze as the EXEPERT
textarea. The model list is authoritative for effort choices and input
modalities; attached images block Send when the selected model does not report
image support. A queued user turn appears immediately, reconciles only with the
authoritative userMessage, and remains visible with Retry and Discard
after a protocol error, timeout, or interrupted runtime generation.
The Codex transcript is a separate CodexTranscriptView entity rather than a
full child tree owned by the workbench. Its top-aligned variable-height
ListState retains the 500-row logical limit while realizing only the viewport
and 512 px of nearby overdraw. Timeline items, pending submissions, approvals,
and the omitted-history notice have stable row keys. Structural reconciliation
uses splice_focusable; content-height changes use remeasure_items, so a
focused control remains available even when its row is outside the viewport.
Wheel animation frames notify this child entity rather than explicitly
notifying the root. The transcript wrapper is a full-height flex container, so
the child receives the remaining stage height instead of collapsing to its
minimum row height. The entity boundary keeps wheel-frame redraws inside the
transcript, reuses unchanged row paint, and leaves the surrounding workbench
untouched.
Each task's process-local scroll bookmark stores the stable top-row key, a fallback index, the offset inside that row, and whether the view was following the tail. Activity filtering, prefix removal, row remeasurement, and task switches restore that anchor. A missing key falls back to the nearest retained index. New transcript content follows automatically only when the reader was already within 48 px of the bottom; otherwise the currently read text remains anchored. Explicit Send and latest/end actions still jump immediately.
Discrete line-based mouse-wheel input uses a restrained 140 ms
ease_out_quint glide. Same-direction steps accumulate up to one viewport;
direction reversal replaces the pending target, and boundaries clamp without
bounce. Pixel-precise touchpad deltas remain direct. Scrollbar interaction,
task/source changes, programmatic jumps, and precise input cancel pending
animation. GPUI's reduced-motion setting also makes line input immediate. This
is native GPUI interpolation, not PhysX and not a browser compositor or CSS
smooth-scroll engine.
The middle-button path is separate from wheel interpolation. It tracks only vertical displacement from its visible anchor, clamps elapsed frame time to 50 ms, and leaves the session armed when a scroll boundary temporarily blocks movement. Pointer buttons, wheel input, Escape, checkpoint activation, programmatic jumps, and transcript scope changes cancel it while preserving the stable transcript bookmark.
Approval cards remain present but disabled after a response is queued and are
removed only after serverRequest/resolved. Permission cards include an
explicit decline. Multi-question fixed-choice requests collect every answer
before submission; secret, free-form, custom-answer, and MCP form prompts expose
their sanitized scope but fail closed to decline/cancel rather than presenting
an unsafe or incomplete acceptance control. A writer-queue rejection re-enables
the matching card instead of leaving it permanently disabled.
Every Codex timeline row owns the transcript width. User and pending-submission bubbles stay content-sized and right-aligned, capped at 760 px. Assistant, reasoning, developer-activity, and approval bodies fill the available row up to the same 760 px cap. Only an inner flexible text region may use a zero minimum width; an otherwise auto-sized body never does. This prevents GPUI from resolving a body to a glyph-wide flex column while still allowing word wrapping at narrow window sizes.
Final agent answers remain primary flat copy. Final and commentary text is
rendered by the native Markdown TextView, keyed by its server turn_id and
item_id, selectable, and allowed to grow within the transcript rather than
creating nested scrolling. Stable keys reuse parsed state across mirror polls;
changed streaming source is reparsed. Headings, lists, emphasis, code, and
tables therefore receive native rich presentation. Commentary remains lighter
progress copy.
Markdown links use GPUI's standard URL handler. Remote Markdown images use its standard image loader and are constrained to the response width. EXEPERT does not add Codex credentials, task identity, workspace data, or custom headers to those media requests. Loading a remote image therefore contacts the host named in the assistant-authored Markdown; users should treat that host as an external privacy boundary. The Copy Visible Transcript action remains verbatim: it copies the original server text, including Markdown source, rather than a rendered reconstruction.
User messages, warnings, assistant commentary, and final answers remain fully mounted. Plans, readable reasoning summaries, commands, output, file changes, MCP, collaboration, web, review, and compaction initially mount only a bounded one-line literal summary. Explicit expansion mounts their complete projected text and allowlisted details; collapse unmounts those bodies while stable-row remeasurement restores the reading anchor. Raw reasoning text is excluded before view state. The context panel reports only App Server-derived operational fields, including model, workspace, effective approval/permission/network settings, writable roots, token usage, rate limits, and sanitized counters. Missing fields say not reported rather than being inferred.
Composer state contract
The readiness matrix below describes the unchanged EXEPERT/Supabase source. Codex uses its independent runtime, model-modality, active-turn steering, and pending-submission gates described above.
Visual styling and request readiness are deliberately separate. The composer evaluates text, current attachments, retained image history, model capability, catalog version, setup state, and request lifecycle before wiring a click handler.
| State | Primary action |
|---|---|
| Whitespace and no validated image | Grey Send; not focusable or clickable |
| Trimmed text or validated image-only turn | Lime/accent Send |
| Image decoding in progress | Disabled with a processing explanation |
| Invalid or oversized attachment | Disabled with the specific attachment error |
| Images with an older text-only backend | Disabled; image input is unavailable |
| Images with a text-only selected model | Disabled; select a model marked Vision |
| Session/model setup or catalog loading | Disabled until verified state is ready |
| Active response | Send becomes Stop in the same slot |
| Cancellation settlement | Disabled until the turn-scoped cancellation event settles |
Stop synchronously queues a turn-scoped local cancellation event. The next desktop event poll settles that event, removes the pending job from the composer, and ignores any late result for that turn; it does not wait for the blocking HTTP worker to return. The desktop HTTP transport and 9Router do not expose a provider-side cancellation protocol, so this behavior must not be described as guaranteed upstream abort or billing cancellation.
Image input
Input paths
The desktop accepts images through:
- the native multi-file picker;
- clipboard paste when the clipboard's authoritative entry is an image or external path; and
- native file drag-and-drop over the composer.
Ordinary clipboard text remains owned by the GPUI textarea. Picker cancellation does not create an error. Adding or removing an image returns focus to the textarea.
Processing and limits
Image work runs on one serial background decoder so four large files cannot multiply peak decoded-memory allocations.
| Limit | Contract |
|---|---|
| Accepted input | PNG, JPEG, WebP |
| Images per current turn | 4 |
| Original file | 20 MiB maximum |
| Original pixel count | 40 megapixels maximum |
| Processed longest edge | 1600 px |
| Processed image | 2 MiB maximum |
| Retained/request image bytes | 6 MiB combined maximum |
Encoded run-chat JSON request body | 9 MiB maximum before JSON parsing |
The decoder verifies the real file format rather than trusting the filename, applies image orientation, resizes with bounded allocation, and re-encodes the processed image. Alpha images remain PNG; opaque images use bounded JPEG encoding. A fifth image and any size-budget violation produce inline copy.
The attachment tray shows a decoded thumbnail, processed format, dimensions, processing or error state, and a remove action. Transcript previews reuse a runtime cache rather than decoding or copying the processed bytes every render.
Ephemeral attachment and compatibility contracts
The additive request types are backward-compatible:
interface ChatImageAttachment {
kind: 'image'
mime_type: 'image/png' | 'image/jpeg' | 'image/webp'
data_base64: string
width: number
height: number
}
interface ChatMessage {
role: 'user' | 'assistant'
content: string
attachments?: ChatImageAttachment[]
}
interface RunChatRequest {
// Existing fields remain unchanged.
user_input: string
attachments?: ChatImageAttachment[]
}
Blank user_input is valid only when the current turn contains at least one
validated attachment. Metadata-only images restored after a restart cannot be
resent or retried until attached again.
Processed bytes use reference-counted memory only in the current desktop process. The live transcript retains no more than one request-sized 6 MiB image budget. Older image allocations are discarded while safe format/dimension metadata remains. New Chat clears the runtime attachments and preview cache.
Transcript JSON never serializes the attachment byte field. After restart, a metadata-only card says Image not retained—attach again instead of showing a broken thumbnail.
Explicit Vision capability
The model catalog advertises:
{
"image_input_version": "image-input-v1",
"models": [
{
"supports_images": true,
"image_capability_source": "provider"
}
]
}
Image support is true only when it comes from one of these sources:
- explicit provider modality/capability fields;
- a route in the server-side
ROUTER_VISION_MODELSJSON array; or - a combo whose known members all support image input.
Unknown or missing provenance is unsupported even if a row says
supports_images: true. Route names are never treated as evidence. Users may
attach images while a text-only model is selected, but Send remains blocked and
the desktop never changes the model automatically.
If a backend does not advertise image-input-v1, a new desktop client remains
safely text-only. Older clients ignore the optional catalog and request fields.
The browser Chat composer intentionally remains text-only.
run-chat validation and routing
Before calling 9Router, the Edge Function:
- reads the JSON body through a bounded stream;
- validates required request fields and the image-only rule;
- validates attachment keys, per-message count, MIME, strict base64, decoded length, declared dimensions, per-item bytes, and combined bytes;
- parses PNG chunks through
IEND, JPEG frame/scan markers throughEOI, or the complete WebP RIFF chunk layout; - confirms the authenticated user owns the requested project;
- resolves the requested route without silent image fallback; and
- rejects image content unless the resolved route has explicit capability.
Valid user turns become OpenAI-compatible content parts containing text and
image_url data parts. The existing exepert-research-v1 structured response,
Journal grounding, brain telemetry block, trace/span persistence, and legacy
SSE compatibility remain intact.
Stable public image failures are:
unsupported_image_model;invalid_image_attachment; andimage_payload_too_large.
The selected model's verified output ceiling now controls both context
reservation and the provider max_tokens request, bounded by the structured or
legacy response limit.
Context measurement
Text-only requests may use a bounded server token estimate when the selected model has a verified context window. Requests containing images do not invent image-token estimates. Until the provider returns prompt usage, their context measurement is unavailable and the UI says so.
When provider prompt_tokens is returned, the server records it as exact
unattributed input usage plus the reserved output segment. This preserves an
exact total without falsely assigning provider-counted image tokens to text
segments.
Privacy and observability
No Supabase Storage bucket or durable cloud image retention is added. The database and local transcript receive metadata, never image content.
Ephemeral does not mean device-only. When the user sends a turn, the processed image bytes cross the authenticated Supabase Edge Function boundary and are forwarded as OpenAI-compatible image content to the explicitly selected 9Router/provider route. The bytes are used for that request and in-memory follow-up context but are excluded from EXEPERT persistence and observability.
Persistence, diagnostics, public errors, traces, and span attributes redact:
data:image/...URLs;data_base64fields;- exact processed attachment encodings; and
- bearer tokens and existing secret patterns.
Observable attachment records are bounded to source, message index where applicable, MIME, dimensions, decoded byte size, total count, and total bytes. Original filenames are never copied into runtime attachment state or persisted records.
Authentication and saved prompts
Desktop Chat bootstraps through the configured client-safe Supabase URL and publishable key. Router and service-role credentials remain inside Edge Functions. The setup flow supports anonymous Chat plus explicit email or OAuth sign-in without writing tokens into the public configuration store.
The Tools menu's Saved prompts action supports local prompt history
throughout a session. Signed-in, non-anonymous users can also list, create,
rename/update, and delete a user-wide saved-prompt library through
chat-saved-prompts.
The chat_saved_prompts table:
- bounds titles to 160 characters and content to 8000 characters;
- indexes prompts by owner and update time;
- enables RLS;
- defines owner-scoped SELECT, INSERT, UPDATE, and DELETE policies; and
- rejects anonymous JWTs even though anonymous users use PostgreSQL's
authenticatedrole.
The Edge Function repeats authentication, non-anonymous, user ownership, and field-bound checks before every library operation.
Configuration and rollout
Server configuration adds:
ROUTER_VISION_MODELS=["provider/explicitly-verified-vision-model"]
This is a server-side allowlist and must never be exposed through VITE_*.
For an image-capable rollout:
- apply the saved-prompt migration if the library is being released;
- deploy
chat-saved-prompts; - configure and verify
ROUTER_VISION_MODELS; - deploy
chat-modelsso clients can observeimage-input-v1; - deploy
run-chat; and - distribute the matching desktop executable.
Deployment and live Vision calls require an explicitly authorized Supabase target. Building the client or passing local tests does not prove a hosted Vision route is configured.
Verification evidence
Automated unit and source-contract coverage includes:
- composer readiness, compact geometry, transcript hierarchy, focus-visible styling, accessibility roles, smart-follow transitions, footer ordering, native Tools semantics, opaque popup contracts, provider-logo fallback, and stable native IDs;
- image-only turns, picker cancellation, text-paste fallback, and drag/drop;
- image formats, orientation, resizing, per-item/combined limits, and a fifth attachment;
- retained-image follow-ups, text-only model gating, retries, cancellation, New Chat cleanup, and metadata-only restart behavior;
- malformed or MIME-mismatched server payloads and truncated containers;
- multimodal provider serialization, capability normalization, combo rules, old-client compatibility, output ceilings, and truthful context usage;
- transcript, error, response, diagnostic, and observability redaction; and
- saved-prompt SQL policy predicates, JWT configuration, and Edge Function dispatch contracts;
- Codex intrinsic user-bubble versus full-width response geometry, the 760 px cap, assistant-only Markdown selection, stable Markdown state identity, hard activity folding, verbatim transcript-copy wiring, virtual-list reconciliation, stable scroll bookmarks, adaptive-scroll boundaries, middle-button velocity/cancellation, virtual checkpoint navigation, and unchanged-snapshot suppression;
- source-qualified Worked/Thought timing placement, provenance precedence, live-to-terminal transitions, exact stopped durations, invalid-data omission, bounded details, DSH reasoning-span merging/deduplication, keyboard and UIA disclosure state, 150 ms/reduced-motion behavior, and the visible 1 Hz ticker;
- centered ZED streaming/footer geometry, presentation-change remeasurement, and the shared 32 px scroll-to-bottom control with its centered 16 px icon and clamped 28-to-32 px appearance endpoints;
- global presentation defaulting and version-2 serialization, responsive
selector IDs, ZED visual hierarchy, Native provisional replacement, shared
transcript bookmarks, Codex disclosure precedence, allowlisted structured
details, the unified
#181818workspace, removed status bar, title-bar utilities, and rail/palette/Attention collaboration placement. These are unit and static contract assertions; a hosted RLS behavioral test still requires an explicitly authorized Supabase target.
The standard local gate is:
pnpm typecheck
pnpm test
pnpm.cmd --dir docs-site exec docusaurus build
cd desktop
cargo +1.96.0-x86_64-pc-windows-msvc fmt --all -- --check
cargo +1.96.0-x86_64-pc-windows-msvc test --workspace --locked --target x86_64-pc-windows-msvc
cargo +1.96.0-x86_64-pc-windows-msvc clippy --workspace --all-targets --locked --target x86_64-pc-windows-msvc -- -D warnings
cargo +1.96.0-x86_64-pc-windows-msvc build -p exepert-desktop --release --locked --target x86_64-pc-windows-msvc
Run the opt-in Codex bounds proof separately and serially because it opens a real native window:
cd desktop
cargo +1.96.0-x86_64-pc-windows-msvc test -p exepert-desktop --test codex_timeline_render --locked --target x86_64-pc-windows-msvc codex_timeline_native_bounds_prevent_glyph_columns -- --ignored --exact --test-threads=1
cargo +1.96.0-x86_64-pc-windows-msvc test -p exepert-desktop --test codex_timeline_render --locked --target x86_64-pc-windows-msvc native_virtual_transcript_realizes_overdraw_in_child_view -- --ignored --exact --test-threads=1
cargo +1.96.0-x86_64-pc-windows-msvc test -p exepert-desktop --lib --locked --target x86_64-pc-windows-msvc ui::transcript::tests::native_auto_scroll_frames_move_the_list_and_paint_the_anchor -- --ignored --exact --test-threads=1
The strict workspace Clippy command above is the release gate and must pass with warnings denied; this feature does not rely on lint allowlists.
git diff --check must also pass. The Rust source-contract tests lock the shared
opaque surface, custom priority-100 wrappers, absence of a second Tools
wrapper, the footprint-preserving padding exchange, and responsive composer
wiring. Pure unit tests prove the 8/10/12-row boundaries, populated-draft
deferral, near-bottom geometry, and ephemeral unseen-response transitions.
Neither launches a Windows window, validates the exposed UI Automation tree,
renders DPI-dependent layouts, or proves pointer, keyboard, scroll, shadow,
paint-order, or actual textarea reflow behavior.
The default Codex width tests inspect the GPUI style contract and source
wiring. A separate opt-in Windows renderer test opens a production GPUI window,
captures first-prepaint bounds, and proves that Hi is wider than its padding,
full bodies reach the 760 px cap, and a 320 px rich response wraps vertically.
The second opt-in harness lays out 500 variable-height transcript rows and 500
independently virtualized checkpoint markers, proves that each remains below 80
realized entries at the harness viewport, and confirms that a child-only notification reuses cached
sibling paint. The third drives production auto-scroll frames against a native
list and proves that the offset changes while the anchor paints and motion stays
armed. They run explicitly with
--ignored --exact --test-threads=1; neither certifies the UIA tree,
network-loaded media, shell URL activation, physical wheel feel, or a particular
Windows display driver. Those remain native interaction evidence.
Native interaction smoke therefore remains a separate release gate. At 1024×640, 1280×720, 1440×800, and 1920×1009, exercise empty, initial-suggestion, long typed and pasted drafts, explicit newlines, width and height resizing, manual clearing, attachment, warning, Send/Stop, failed/Retry, feedback, and long-transcript states. Verify the 8/10/12-row caps, upward growth, internal scrolling after the cap, stable populated drafts across breakpoints, near-bottom preservation, Tab and Shift+Tab order, keyboard focus rings, Windows UIA roles and accessible names, Tools arrow/Enter/Escape behavior, disabled menu items, model-name truncation, Send/Stop alignment, scroll-to-bottom arrow and unseen-dot behavior, DPI scaling, and unclipped popovers and drop overlay. Confirm visually that no transcript or composer line shows through the Tools, model, or prompt surfaces. Live Supabase and Vision-model requests stay disabled unless the exact hosted target is separately authorized.
For Codex transcript smoke, mirror the existing short Hi and longer rich response tasks at 1024×640, 1280×720, and 1920×1009. Confirm that short user bubbles remain readable, assistant and developer bodies use the available width, headings/lists/code wrap without horizontal clipping, selection and Copy Visible Transcript preserve their distinct rendered/source behavior, and transcript scrolling remains single-layer. Exercise Activity shown/hidden, mouse-wheel glide, middle-button auto-scroll and every cancellation path, precision touchpad input, reduced motion, expansion controls, checkpoint mouse/keyboard navigation, task switching, live mirror updates, and tail following; confirm that collapsed activity never exposes a long body and an unchanged one-second poll creates no visible hitch. Link activation and remote image loading are manual networked checks; use a benign public target and confirm the request carries no Codex credential or task metadata.
Related documentation
- Desktop overview
- Native desktop performance analysis
- Rust and GPUI optimization playbook
- Browser Brain Chat
- Supabase API contracts
- Testing and CI
- Native desktop renderer gate
- Native Codex live client
- Zed agent UI lessons for EXEPERT
- Native Chat and Codex delivery record
- Native response timing and alignment delivery record