a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
779 lines
21 KiB
Markdown
779 lines
21 KiB
Markdown
# Desktop Codebase Architecture from the Existing TUI
|
|
|
|
Status: Proposed
|
|
Updated: 2026-04-25
|
|
|
|
This document translates the current Jcode TUI architecture into a concrete codebase plan for a future custom desktop app.
|
|
|
|
The desktop app is expected to have roughly the same product capabilities as the TUI, but it should not be a direct port of the TUI implementation. The TUI is terminal/cell-oriented and has accumulated a large amount of terminal-specific rendering, input, layout, scrolling, and cache logic. The desktop app should reuse the runtime/protocol/session concepts and some presentation models, but it should have a separate custom UI and rendering architecture.
|
|
|
|
See also:
|
|
|
|
- [`DESKTOP_APP_ARCHITECTURE.md`](./DESKTOP_APP_ARCHITECTURE.md)
|
|
- [`DESKTOP_SUPERAPP_WORKSPACE.md`](./DESKTOP_SUPERAPP_WORKSPACE.md)
|
|
- [`CLIENT_CORE_PRESENTATION_SPLIT_PLAN.md`](./CLIENT_CORE_PRESENTATION_SPLIT_PLAN.md)
|
|
- [`MULTI_SESSION_CLIENT_ARCHITECTURE.md`](./MULTI_SESSION_CLIENT_ARCHITECTURE.md)
|
|
- [`SERVER_ARCHITECTURE.md`](./SERVER_ARCHITECTURE.md)
|
|
|
|
## Current TUI observations
|
|
|
|
The current TUI is feature-rich and should be treated as the product reference implementation.
|
|
|
|
Approximate current size:
|
|
|
|
```text
|
|
src/tui/*.rs and submodules: 144 Rust files, ~115k lines
|
|
src/tui/app.rs: ~800 lines
|
|
src/tui/ui.rs: ~3.8k lines
|
|
src/tui/ui_prepare.rs: ~1.6k lines
|
|
src/tui/ui_viewport.rs: ~750 lines
|
|
src/tui/ui_messages.rs: ~2.4k lines
|
|
src/tui/markdown.rs: ~1.4k lines
|
|
src/protocol.rs: ~1.4k lines
|
|
```
|
|
|
|
Important existing pieces:
|
|
|
|
- `src/protocol.rs`
|
|
- newline-delimited JSON over Unix socket
|
|
- `Request`
|
|
- `ServerEvent`
|
|
- session subscribe/resume/history/message/cancel/tool/status events
|
|
- `src/server/`
|
|
- multi-client server/session runtime
|
|
- reconnect support
|
|
- session lifecycle
|
|
- client events
|
|
- background tasks/swarm/comm state
|
|
- `src/tui/app.rs` and `src/tui/app/*`
|
|
- TUI app state root
|
|
- input handling
|
|
- remote connection handling
|
|
- command parsing
|
|
- server event reducer
|
|
- local mode support
|
|
- copy/selection/navigation/session picker/debug overlays
|
|
- `src/tui/ui.rs` and `src/tui/ui_*`
|
|
- ratatui renderer
|
|
- terminal/cell layout
|
|
- viewport and scroll behavior
|
|
- side panes
|
|
- overlays
|
|
- visual debug capture
|
|
- `src/tui/ui_prepare.rs`
|
|
- frame preparation
|
|
- wrapped line maps
|
|
- full prep cache
|
|
- body/streaming/batch preparation
|
|
- `src/tui/ui_messages.rs`
|
|
- message-to-terminal-line rendering
|
|
- tool/system/background/swarm usage rendering
|
|
- line caches
|
|
- `src/tui/markdown.rs`
|
|
- terminal markdown rendering
|
|
- syntax highlighting cache
|
|
- mermaid integration hooks
|
|
|
|
## Key lesson
|
|
|
|
The TUI already has the right **feature set** and many correct **domain concepts**, but it does not yet have the right boundaries for a custom desktop UI.
|
|
|
|
The desktop should not import `ratatui::Line`, terminal-width wrapping, global renderer caches, or terminal input assumptions into core app state.
|
|
|
|
The desktop should instead use this split:
|
|
|
|
```text
|
|
Jcode server/runtime/protocol
|
|
-> client-core reducer and view model
|
|
-> desktop product views
|
|
-> custom UI tree/layout
|
|
-> display list
|
|
-> wgpu renderer
|
|
```
|
|
|
|
## What to reuse versus not reuse
|
|
|
|
### Reuse directly or almost directly
|
|
|
|
- server process architecture
|
|
- session ownership model
|
|
- reconnect semantics
|
|
- request/event protocol as the starting point
|
|
- server-side session history and tool execution
|
|
- model/provider/session metadata
|
|
- command concepts
|
|
- background task concepts
|
|
- swarm/goal/activity concepts
|
|
- permission concepts
|
|
- debug/diagnostic philosophy
|
|
|
|
### Reuse after extracting away terminal types
|
|
|
|
- server event reduction logic from `src/tui/app/remote/server_events.rs`
|
|
- message display block construction
|
|
- tool call summaries
|
|
- activity/status models
|
|
- markdown block parsing decisions
|
|
- copy target semantics
|
|
- session picker data model
|
|
- login/account picker data model
|
|
- command registry and command metadata
|
|
- info widget data models
|
|
- memory/debug summary models
|
|
|
|
### Do not reuse directly
|
|
|
|
- `ratatui::Line` as a cross-surface representation
|
|
- terminal cell layout
|
|
- terminal-specific scroll offsets as the primary desktop model
|
|
- global renderer state such as `LAST_MAX_SCROLL`-style globals
|
|
- terminal key protocol code
|
|
- terminal-specific markdown wrapping
|
|
- terminal-specific image/mermaid display code
|
|
- the giant `TuiState` trait as the desktop boundary
|
|
- monolithic `App` state with runtime, transport, UI, and render concerns mixed together
|
|
|
|
## The main architectural risk
|
|
|
|
If desktop development copies the TUI structure directly, the result will likely be:
|
|
|
|
- another very large `DesktopApp` state object
|
|
- rendering caches mixed with domain state
|
|
- platform input handling mixed with session reducers
|
|
- duplicated command logic
|
|
- duplicated server event handling
|
|
- hard-to-test UI behavior
|
|
- difficulty sharing behavior between TUI and desktop
|
|
|
|
The desktop app should avoid repeating this by creating a real client-core boundary before implementing too many features.
|
|
|
|
## Proposed crate/module architecture
|
|
|
|
The exact crate names can change, but the dependency direction should not.
|
|
|
|
```text
|
|
crates/
|
|
jcode-protocol/ # eventually extracted from src/protocol.rs
|
|
jcode-client-core/ # surface-independent client state/reducers/view models
|
|
jcode-desktop-ui/ # custom UI tree, layout, input routing, style tokens
|
|
jcode-desktop-renderer/ # wgpu renderer, display list, glyph/image atlases
|
|
jcode-desktop-platform/ # winit/AppKit/Linux shell abstraction, menus, clipboard
|
|
jcode-desktop/ # product app: windows, panels, protocol client, composition
|
|
```
|
|
|
|
Initial implementation may keep some of these as modules inside `crates/jcode-desktop` to reduce early friction, but the boundaries should be designed as if they were separate crates.
|
|
|
|
## Dependency direction
|
|
|
|
Allowed dependency flow:
|
|
|
|
```text
|
|
jcode-desktop
|
|
-> jcode-desktop-platform
|
|
-> jcode-desktop-renderer
|
|
-> jcode-desktop-ui
|
|
-> jcode-client-core
|
|
-> jcode-protocol
|
|
```
|
|
|
|
`jcode-client-core` must not depend on:
|
|
|
|
- `wgpu`
|
|
- `winit`
|
|
- AppKit
|
|
- Wayland/X11
|
|
- `ratatui`
|
|
- `crossterm`
|
|
- terminal markdown rendering
|
|
|
|
`jcode-desktop-ui` should not depend on the Jcode server runtime. It can depend on client-core view models and generic UI types.
|
|
|
|
`jcode-desktop-renderer` should not know what a Jcode session is. It renders display lists, text runs, images, clips, and primitives.
|
|
|
|
## `jcode-protocol`
|
|
|
|
The existing `src/protocol.rs` is already the natural starting point.
|
|
|
|
Long-term, extract it into a crate so both TUI and desktop consume the same wire types:
|
|
|
|
```text
|
|
crates/jcode-protocol/src/lib.rs
|
|
Request
|
|
ServerEvent
|
|
HistoryMessage
|
|
SessionActivitySnapshot
|
|
FeatureToggle
|
|
SwarmMemberStatus
|
|
protocol version/capability handshake types
|
|
```
|
|
|
|
Short-term, desktop can import the root crate types, but the protocol should be treated as shared API.
|
|
|
|
Desktop-specific protocol needs may include:
|
|
|
|
- session list with metadata optimized for a sidebar
|
|
- event cursors for resumable subscriptions
|
|
- richer activity snapshots
|
|
- workspace/git summary snapshots
|
|
- permission request snapshots
|
|
- changed-file summaries
|
|
- surface/window attachment metadata
|
|
|
|
Avoid making a second unrelated desktop protocol unless the existing protocol becomes a blocker.
|
|
|
|
## `jcode-client-core`
|
|
|
|
This is the most important shared layer.
|
|
|
|
It should own behavior and state that are independent of the terminal and independent of desktop rendering.
|
|
|
|
Suggested modules:
|
|
|
|
```text
|
|
jcode-client-core/
|
|
src/
|
|
lib.rs
|
|
app_model.rs
|
|
actions.rs
|
|
reducer.rs
|
|
protocol_reducer.rs
|
|
command_registry.rs
|
|
session_list.rs
|
|
transcript.rs
|
|
transcript_blocks.rs
|
|
composer.rs
|
|
activity.rs
|
|
permissions.rs
|
|
workspace.rs
|
|
git.rs
|
|
settings.rs
|
|
overlays.rs
|
|
selection.rs
|
|
status.rs
|
|
diagnostics.rs
|
|
view_model.rs
|
|
```
|
|
|
|
### Core state slices
|
|
|
|
```rust
|
|
struct ClientCore {
|
|
sessions: SessionListState,
|
|
active_surface: Option<SurfaceId>,
|
|
surfaces: SurfaceMap,
|
|
connection: ConnectionState,
|
|
commands: CommandRegistry,
|
|
activity: ActivityState,
|
|
permissions: PermissionState,
|
|
workspace: WorkspaceState,
|
|
diagnostics: DiagnosticsState,
|
|
}
|
|
```
|
|
|
|
Each surface owns local UI state:
|
|
|
|
```rust
|
|
struct SurfaceState {
|
|
session_id: SessionId,
|
|
transcript: TranscriptState,
|
|
composer: ComposerState,
|
|
selection: SelectionState,
|
|
scroll: ScrollState,
|
|
focused_region: FocusRegion,
|
|
overlays: OverlayStack,
|
|
pane_layout: PaneLayoutState,
|
|
}
|
|
```
|
|
|
|
The important rule:
|
|
|
|
> Server-owned session state and surface-local UI state must remain separate.
|
|
|
|
This matches the existing multi-session architecture docs and makes desktop multi-window/multi-surface possible later.
|
|
|
|
### Actions and reducers
|
|
|
|
Desktop and TUI should eventually share reducer logic through typed actions:
|
|
|
|
```rust
|
|
pub enum ClientAction {
|
|
Platform(PlatformAction),
|
|
User(UserAction),
|
|
Protocol(ServerEvent),
|
|
Tick(TickKind),
|
|
Command(CommandId),
|
|
}
|
|
```
|
|
|
|
Examples:
|
|
|
|
```rust
|
|
pub enum UserAction {
|
|
SubmitPrompt,
|
|
EditComposer(ComposerEdit),
|
|
ScrollTranscript { delta_px: f32 },
|
|
SelectSession(SessionId),
|
|
CancelRun,
|
|
ToggleActivityPanel,
|
|
OpenCommandPalette,
|
|
}
|
|
```
|
|
|
|
Reducers should return effects rather than performing side effects directly:
|
|
|
|
```rust
|
|
pub enum ClientEffect {
|
|
SendRequest(Request),
|
|
StartDaemon,
|
|
OpenExternalEditor(PathBuf),
|
|
CopyToClipboard(String),
|
|
ShowNotification(Notification),
|
|
RequestRender,
|
|
}
|
|
```
|
|
|
|
This is the clean boundary that the current TUI mostly lacks.
|
|
|
|
## Transcript model
|
|
|
|
The TUI currently reduces history/events into `DisplayMessage` and then terminal lines. Desktop needs a richer block model.
|
|
|
|
Suggested model:
|
|
|
|
```rust
|
|
struct TranscriptState {
|
|
blocks: Vec<TranscriptBlock>,
|
|
block_index: HashMap<BlockId, usize>,
|
|
streaming_block: Option<BlockId>,
|
|
version: u64,
|
|
}
|
|
|
|
enum TranscriptBlock {
|
|
User(UserBlock),
|
|
Assistant(AssistantBlock),
|
|
Tool(ToolBlock),
|
|
System(SystemBlock),
|
|
BackgroundTask(TaskBlock),
|
|
Swarm(SwarmBlock),
|
|
Usage(UsageBlock),
|
|
Memory(MemoryBlock),
|
|
Compaction(CompactionBlock),
|
|
}
|
|
```
|
|
|
|
This becomes the common semantic representation.
|
|
|
|
The TUI can continue rendering terminal lines from this model later. The desktop will render custom cards/rows from it.
|
|
|
|
### Desktop rendering path
|
|
|
|
```text
|
|
TranscriptBlock
|
|
-> DesktopTimelineItem
|
|
-> UI nodes
|
|
-> layout boxes
|
|
-> text layout runs
|
|
-> display list
|
|
```
|
|
|
|
Do not use terminal wrapped lines as the desktop source of truth.
|
|
|
|
## Feature mapping from TUI to desktop
|
|
|
|
| TUI feature | Current TUI shape | Desktop architecture |
|
|
|---|---|---|
|
|
| Chat transcript | `DisplayMessage` + wrapped `Line`s | `TranscriptBlock` + virtualized timeline |
|
|
| Streaming assistant text | `streaming_text` + incremental markdown | append-aware block text cache |
|
|
| Tool calls | `ToolCall` display messages and streaming tool calls | tool cards with compact/expanded states |
|
|
| Batch progress | `BatchProgress` in status/message prep | activity item + inline timeline block |
|
|
| Composer | terminal input string/cursor | custom text input model, IME-aware later |
|
|
| Queued messages | app queue fields | composer/session queue state in client-core |
|
|
| Soft interrupts | protocol events and pending queue | visible interruption banner/queue chip |
|
|
| Header/status | `ui_header`, `info_widget` | top bar + status/activity regions |
|
|
| Side pane | pinned diff/content/diagram pane | inspector panel with tabs |
|
|
| Mermaid/diagrams | terminal/image pane | image/vector surface, later side inspector |
|
|
| Diffs | terminal inline/pinned/file modes | changed files panel, diff cards, later hunk UI |
|
|
| Session picker | modal overlay | command palette/session switcher modal |
|
|
| Login/account picker | terminal overlays | settings/account modal views |
|
|
| Commands | slash commands/key handlers | shared command registry + palette + menus |
|
|
| Copy selection | line/cell ranges | semantic block/text selection |
|
|
| Workspace map | TUI workspace widget | session/workspace sidebar, optional spatial mode |
|
|
| Debug overlays | visual debug/frame capture | performance HUD + UI tree/render inspector |
|
|
| Reconnect | remote loop state machine | protocol client state machine in client-core/app |
|
|
| Replay | replay mode | event-log replay harness for desktop UI tests |
|
|
|
|
## Desktop product module layout
|
|
|
|
`jcode-desktop` should be product composition, not renderer internals.
|
|
|
|
Suggested modules:
|
|
|
|
```text
|
|
crates/jcode-desktop/src/
|
|
main.rs
|
|
app.rs # top-level DesktopApp orchestration
|
|
config.rs
|
|
protocol_client.rs # socket connection, read/write tasks
|
|
daemon.rs # start/connect/find bundled daemon
|
|
views/
|
|
root.rs
|
|
top_bar.rs
|
|
session_sidebar.rs
|
|
timeline.rs
|
|
timeline_blocks.rs
|
|
composer.rs
|
|
activity_panel.rs
|
|
workspace_panel.rs
|
|
inspector_panel.rs
|
|
command_palette.rs
|
|
permission_modal.rs
|
|
settings.rs
|
|
debug_hud.rs
|
|
reducers/
|
|
platform_events.rs
|
|
commands.rs
|
|
view_actions.rs
|
|
macos/
|
|
bundle.rs # build/package helpers if needed
|
|
appkit_hooks.rs # menus/lifecycle if winit is insufficient
|
|
```
|
|
|
|
## Custom UI crate layout
|
|
|
|
`jcode-desktop-ui` is the framework-like internal layer, but it is product-owned and small.
|
|
|
|
```text
|
|
crates/jcode-desktop-ui/src/
|
|
lib.rs
|
|
id.rs
|
|
geometry.rs
|
|
color.rs
|
|
style.rs
|
|
theme.rs
|
|
input.rs
|
|
focus.rs
|
|
accessibility.rs # semantic tree placeholder, not full impl initially
|
|
tree.rs # retained node tree
|
|
widget.rs # view builder traits/types
|
|
layout/
|
|
mod.rs
|
|
flex.rs
|
|
stack.rs
|
|
split.rs
|
|
scroll.rs
|
|
virtual_list.rs
|
|
text/
|
|
mod.rs
|
|
buffer.rs
|
|
selection.rs
|
|
shaping.rs
|
|
cache.rs
|
|
display_list.rs
|
|
invalidation.rs
|
|
animation.rs # minimal timers only, no full animation system initially
|
|
debug.rs
|
|
```
|
|
|
|
This crate should expose primitives such as:
|
|
|
|
```rust
|
|
pub enum UiNodeKind {
|
|
Row,
|
|
Column,
|
|
Stack,
|
|
SplitPane,
|
|
Scroll,
|
|
VirtualList,
|
|
Text,
|
|
TextInput,
|
|
Button,
|
|
CustomPaint,
|
|
}
|
|
```
|
|
|
|
But product views should mostly build specialized surfaces rather than generic widget soup.
|
|
|
|
## Renderer crate layout
|
|
|
|
`jcode-desktop-renderer` should know nothing about Jcode.
|
|
|
|
```text
|
|
crates/jcode-desktop-renderer/src/
|
|
lib.rs
|
|
gpu.rs
|
|
surface.rs
|
|
pipeline.rs
|
|
primitives.rs
|
|
text_renderer.rs
|
|
glyph_atlas.rs
|
|
image_atlas.rs
|
|
clips.rs
|
|
stats.rs
|
|
screenshot.rs
|
|
```
|
|
|
|
Input:
|
|
|
|
```rust
|
|
struct DisplayList {
|
|
commands: Vec<DrawCommand>,
|
|
}
|
|
|
|
enum DrawCommand {
|
|
Rect(RectPaint),
|
|
Border(BorderPaint),
|
|
Text(TextPaint),
|
|
Image(ImagePaint),
|
|
ClipBegin(Rect),
|
|
ClipEnd,
|
|
}
|
|
```
|
|
|
|
Output:
|
|
|
|
- frame rendered
|
|
- renderer stats
|
|
- optional screenshot/debug capture
|
|
|
|
The renderer should be testable with deterministic display lists and should support headless/golden rendering later if practical.
|
|
|
|
## Platform crate layout
|
|
|
|
Start with `winit`, but avoid spreading `winit` types through the product.
|
|
|
|
```text
|
|
crates/jcode-desktop-platform/src/
|
|
lib.rs
|
|
event.rs
|
|
window.rs
|
|
clipboard.rs
|
|
menus.rs
|
|
dialogs.rs
|
|
appearance.rs
|
|
shortcuts.rs
|
|
macos.rs
|
|
linux.rs
|
|
```
|
|
|
|
Normalize platform differences:
|
|
|
|
```rust
|
|
enum PlatformEvent {
|
|
WindowResized { size: PhysicalSize, scale: f64 },
|
|
ScaleFactorChanged { scale: f64 },
|
|
RedrawRequested,
|
|
Keyboard(KeyboardEvent),
|
|
Pointer(PointerEvent),
|
|
Scroll(ScrollEvent),
|
|
FilesDropped(Vec<PathBuf>),
|
|
AppearanceChanged(Appearance),
|
|
AppShouldQuit,
|
|
}
|
|
```
|
|
|
|
Keyboard shortcuts should use platform semantic modifiers:
|
|
|
|
```rust
|
|
enum ShortcutModifier {
|
|
Primary, // Cmd on macOS, Ctrl elsewhere
|
|
Alt,
|
|
Shift,
|
|
Control,
|
|
Command,
|
|
}
|
|
```
|
|
|
|
## Render/update loop
|
|
|
|
The TUI uses a redraw interval and `needs_redraw`. Desktop should keep the same spirit but be stricter.
|
|
|
|
```text
|
|
wait for platform/protocol/timer event
|
|
-> normalize event
|
|
-> reducer updates client-core/app state
|
|
-> collect effects
|
|
-> mark dirty UI nodes
|
|
-> if render requested:
|
|
layout dirty/visible nodes
|
|
shape dirty text
|
|
build display list
|
|
submit wgpu frame
|
|
publish debug stats
|
|
```
|
|
|
|
Rules:
|
|
|
|
- no continuous render loop when idle
|
|
- no full transcript re-layout on token append
|
|
- no unbounded visible node count
|
|
- protocol events may coalesce before rendering
|
|
- animations must explicitly schedule the next frame
|
|
- frame stats should be available before real feature integration is considered done
|
|
|
|
## Reuse path for existing TUI behavior
|
|
|
|
Do not stop all TUI work to extract everything first. Use an incremental route.
|
|
|
|
### Phase 1: desktop prototype independent of TUI internals
|
|
|
|
Build:
|
|
|
|
- desktop crates/modules
|
|
- fake transcript/activity data
|
|
- virtualized timeline
|
|
- debug HUD
|
|
- protocol-shaped fake events
|
|
|
|
Avoid depending on `src/tui`.
|
|
|
|
### Phase 2: protocol reuse
|
|
|
|
Use the existing server protocol:
|
|
|
|
- connect to `jcode serve`
|
|
- subscribe/resume session
|
|
- receive `ServerEvent`
|
|
- send `Request::Message`, `Request::Cancel`, etc.
|
|
|
|
Implement a desktop protocol reducer that mirrors the important behavior in `src/tui/app/remote/server_events.rs`, but writes to `ClientCore`/`TranscriptBlock`, not `DisplayMessage`.
|
|
|
|
### Phase 3: extract client-core
|
|
|
|
Once the desktop reducer shape is clear, extract shared pieces from TUI and desktop into `jcode-client-core`:
|
|
|
|
- transcript block model
|
|
- server event reducer
|
|
- command registry metadata
|
|
- activity model
|
|
- status model
|
|
- session list model
|
|
- permission model
|
|
|
|
At that point the TUI can gradually become another presentation of `jcode-client-core`, but it does not have to be converted all at once.
|
|
|
|
### Phase 4: feature parity
|
|
|
|
Add desktop versions of TUI features in priority order:
|
|
|
|
1. sessions, transcript, composer, send/cancel
|
|
2. tool cards and streaming output
|
|
3. activity panel and background tasks
|
|
4. command palette and core slash commands
|
|
5. permission prompts
|
|
6. session picker/resume/search
|
|
7. workspace/git/changed files
|
|
8. settings/login/account surfaces
|
|
9. diff/diagram inspector
|
|
10. debug/replay/profiling surfaces
|
|
|
|
## Desktop should be server-first
|
|
|
|
The TUI still supports local mode and remote/server mode. The desktop should start server-first.
|
|
|
|
Recommended desktop rule:
|
|
|
|
> Desktop always connects to a local Jcode server/daemon. It does not embed the agent runtime in-process.
|
|
|
|
Reasons:
|
|
|
|
- avoids UI freezes from runtime work
|
|
- keeps CLI/TUI/desktop as peers
|
|
- reuses reconnect/session lifecycle
|
|
- simpler crash isolation
|
|
- easier macOS bundle helper model
|
|
- avoids another local-mode runtime path
|
|
|
|
## Differences from the TUI model
|
|
|
|
### Scrolling
|
|
|
|
TUI scroll is line/cell based. Desktop scroll should be pixel based with fractional offsets.
|
|
|
|
```rust
|
|
struct ScrollState {
|
|
offset_px: f32,
|
|
velocity_px: f32,
|
|
anchor: Option<ScrollAnchor>,
|
|
auto_scroll: bool,
|
|
}
|
|
```
|
|
|
|
Virtualization should happen by pixel range and estimated/measured block heights.
|
|
|
|
### Text
|
|
|
|
TUI text is terminal spans and display widths. Desktop text should be shaped runs and glyph positions.
|
|
|
|
Desktop text caches should be keyed by:
|
|
|
|
- block ID
|
|
- content version/hash
|
|
- style
|
|
- available width
|
|
- font scale
|
|
- platform scale factor
|
|
|
|
### Selection
|
|
|
|
TUI selection is line/cell based. Desktop should use semantic selection:
|
|
|
|
- block ID
|
|
- text range within block
|
|
- optional structured copy target
|
|
|
|
### Layout
|
|
|
|
TUI layout is frame-sized terminal rects. Desktop should use a retained layout tree with dirty flags.
|
|
|
|
### Rendering caches
|
|
|
|
The TUI has several global caches. Desktop caches should be instance-owned and attributable:
|
|
|
|
```rust
|
|
struct RenderCaches {
|
|
text: TextLayoutCache,
|
|
glyphs: GlyphAtlas,
|
|
images: ImageAtlas,
|
|
timeline_measurements: MeasurementCache,
|
|
}
|
|
```
|
|
|
|
No process-global renderer state unless it is explicitly immutable/static.
|
|
|
|
## Testing strategy
|
|
|
|
Desktop should borrow the TUI's debug-first mentality, but use desktop-appropriate tests.
|
|
|
|
Required early tests:
|
|
|
|
- protocol reducer tests from `ServerEvent` sequences to `TranscriptBlock` state
|
|
- transcript virtualization tests with 100k fake blocks
|
|
- scroll anchor tests during streaming append
|
|
- layout tests for split panes and timeline rows
|
|
- text cache invalidation tests
|
|
- command registry tests
|
|
- display-list snapshot tests for stable fake UI states
|
|
- replay tests using captured protocol event logs
|
|
|
|
Avoid depending on GPU tests for basic correctness. Most UI behavior should be validated before `wgpu` submission.
|
|
|
|
## Implementation recommendation
|
|
|
|
Start by adding desktop code without touching the TUI too much:
|
|
|
|
```text
|
|
crates/jcode-desktop-ui # pure-ish UI/layout model
|
|
crates/jcode-desktop-renderer # wgpu display-list renderer
|
|
crates/jcode-desktop # fake-data app shell
|
|
```
|
|
|
|
Then connect to the server protocol.
|
|
|
|
Only after the desktop reducer/view model shape is proven should shared `jcode-client-core` extraction begin. This avoids prematurely extracting the wrong abstraction from the current TUI.
|
|
|
|
## Summary decision
|
|
|
|
The desktop app should be architected as a new custom presentation stack over shared client/runtime concepts, not as a ratatui port.
|
|
|
|
The TUI remains the feature reference. The server/protocol remains the runtime foundation. The new shared layer should be `client-core`, which owns surface-independent app behavior and view models. The desktop-specific code should focus on platform integration, retained UI, custom layout, text shaping, virtualization, and `wgpu` rendering.
|