CLIENT_CORE.md is the map for the crate: what holds what against the Kotlin it replaces, how many tests were ported per file (85 total, test-for-test where the Kotlin had JVM tests), what api.rs and transcript_fold.rs cover versus don't yet, and the two things left deliberately undone with reasons (TranscriptUnits.kt's Compose-specific flatten, and event_model::Event's missing Unknown catch-all). run-tests.sh now loops event-model, client-core and server rather than only server, so the new crates' tests run from the same one command AGENTS.md already points at. Note for whoever merges this into rustify: this worktree branched before RUST.md existed there, so I could not apply the requested edit to its "Where things stand" bullet without an add/add conflict against concurrent work on that file. Suggested wording is in this commit's message on the orchestrator side -- apply directly to rustify's RUST.md: mark item 1 of the Recommendation and the "Not started: client-core" bullet as done, pointing at client-core/ and CLIENT_CORE.md, dated 2026-09-04. Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
131 lines
7.8 KiB
Markdown
131 lines
7.8 KiB
Markdown
# client-core
|
|
|
|
`client-core/` is the app's pure logic held once instead of twice, per
|
|
RUST.md's recommendation item 1. It is a plain Rust library crate with no UI
|
|
framework dependency of any kind, so it can outlive whichever one the app
|
|
ends up drawing with (Masonry, iris, or something else -- see RUST.md).
|
|
`event-model/` is its sibling: the wire shape both this crate and `server/`
|
|
share, extracted from `server/src/session/driver.rs` and
|
|
`session/transcript.rs` on 2026-09-04.
|
|
|
|
Neither crate is wired into anything yet. `server/` re-exports `event-model`
|
|
so its own behaviour is unchanged (`./run-tests.sh` covers it); `client-core`
|
|
has no caller -- it exists for whichever experiment in RUST.md picks it up
|
|
next (a Masonry or iris transcript screen, most likely).
|
|
|
|
## What's here, and what Kotlin file it replaces
|
|
|
|
| `client-core/src/…` | Kotlin original | Status |
|
|
|------------------------------------------|-------------------------------------------|--------|
|
|
| `event-model/src/lib.rs` (shared crate) | `Events.kt` (the enum mirror) | Done |
|
|
| `ansi.rs` | `Ansi.kt` | Done, ported test-for-test |
|
|
| `highlight/mod.rs`, `languages.rs` | `Highlighter.kt`, `Languages.kt` | Done, ported test-for-test |
|
|
| `highlight/markdown.rs` | `MarkdownSyntax.kt` | Done, ported test-for-test |
|
|
| `transcript_cache.rs` | `TranscriptCache.kt` | Done, ported test-for-test |
|
|
| `sse.rs` | `Sse.kt` (the framing half) | Done, new tests (Kotlin had none of its own beyond integration) |
|
|
| `api.rs` | `Api.kt` | Partial -- see below |
|
|
| `event_stream.rs` | `EventStream.kt` | Done |
|
|
| `transcript_fold.rs` | `TranscriptItems.kt`, `ToolRows.kt` | Partial -- see below |
|
|
| *(not started)* | `TranscriptSource.kt` | Not started |
|
|
| *(not ported, and may never be)* | `TranscriptUnits.kt` | Out of scope -- see below |
|
|
|
|
Every file above whose Kotlin counterpart had a JVM unit test (`AnsiTest`,
|
|
`HighlighterTest`, `TranscriptCacheTest`) has had every one of those test
|
|
cases ported alongside it, plus new tests for the pieces that had none
|
|
(`sse.rs`, `api.rs`, `event_stream.rs`, `transcript_fold.rs`). Test count by
|
|
crate as of this writing: **85 in `client-core`**, 0 in `event-model` (its
|
|
types carry no logic of their own to test -- `server/`'s own tests exercise
|
|
them via `session::transcript`'s round-trip coverage).
|
|
|
|
## Correspondence notes worth knowing before touching either side
|
|
|
|
- **`ansi.rs`'s `StyledText`/`Style`/`Rgb`** stand in for Compose's
|
|
`AnnotatedString`/`SpanStyle`/`Color`, since this crate has no Compose.
|
|
`StyledText` is plain text plus a `Vec<(Range<usize>, Style)>` of
|
|
non-overlapping spans. Whatever UI framework ends up consuming this
|
|
crate maps `Style` onto its own text-styling type; nothing here should
|
|
change to accommodate a particular one.
|
|
- **`highlight`'s `Span`/`Kind`** use **char indices, not byte offsets**
|
|
(`Vec<char>` internally), mirroring the Kotlin original's `Char`-indexed
|
|
strings. `highlight::span_text` turns a `Span` back into text for a
|
|
caller working the same way; a caller that wants byte offsets into a
|
|
`&str` has to convert.
|
|
- **`transcript_cache.rs`'s `SessionCache::guard`** found a real
|
|
translation bug while it was being written: an early draft let a
|
|
*damaged* chunk (one file unreadable, discard just this session) and a
|
|
genuine I/O failure (disk gone, disable the whole cache) both surface as
|
|
the same `Err` from one closure, which would have disabled every
|
|
session's cache over a single corrupt chunk. Fixed by checking a
|
|
thread-local "was this damage" flag before deciding which failure mode
|
|
it was -- see the comment on `guard` and the commit message for
|
|
`transcript_cache.rs`.
|
|
|
|
## What `api.rs` covers, and what it does not yet
|
|
|
|
`ApiClient` wraps a `Transport` trait (network I/O kept out from behind, so
|
|
`ApiClient` and `event_stream::follow_session_events` are tested with a
|
|
fake transport and no server). `UreqTransport` is the only real
|
|
implementation, backed by `ureq` -- see its Cargo.toml comment for why
|
|
(blocking, already a project dependency, no extra TLS crate needed since
|
|
`ureq::tls::Certificate::from_pem` reads the pinned CA directly).
|
|
|
|
Covered: session list/read, message send, unqueue, answer, interrupt,
|
|
stop, start, rename, cwd, model, permission-mode, notify, command,
|
|
compact, delete, and one transcript page.
|
|
|
|
**Not covered, and each is real work rather than a stub to fill in:**
|
|
setups (`/setups*`, machine and provider discovery), the file explorer
|
|
(`/setups/{id}/dir|file`), usage (`/usage`), models
|
|
(`/models*`, HuggingFace browsing and downloads), attachments
|
|
(`/sessions/{id}/attachments`), importing (`/setups/{id}/importable*`),
|
|
and the `/notifications` stream. `server/src/routes.rs`'s module doc is
|
|
the full table to work from when one of these is next.
|
|
|
|
## What `transcript_fold.rs` covers, and what it does not yet
|
|
|
|
`fold_event` covers every `Event` variant server/ can produce today,
|
|
including tool-call/question/image attachment and peer-message placement.
|
|
`group_tool_runs` groups adjacent calls into `TranscriptRow::Tools`.
|
|
|
|
**Not ported:** `TranscriptItems.kt`'s `joinPages` (and its
|
|
`healSplitMessage`/`adoptRun` helpers) -- the page-boundary healing that
|
|
merges a tool call split across two fetched pages and re-merges a run a
|
|
boundary cut through. This matters the moment paging backward through
|
|
history is exercised; it is deliberately left rather than rushed, since
|
|
it is exactly the kind of boundary logic this project's own "things that
|
|
have bitten" section warns reads fine and is wrong at the edges.
|
|
|
|
**Known gap, and a decision for whoever closes it:** `event_model::Event`
|
|
has no `Unknown`/catch-all variant, unlike `Events.kt`'s hand-kept mirror.
|
|
A server newer than this build that adds an event type will fail to parse
|
|
that line rather than degrading to a placeholder row. Closing this means
|
|
deciding how `event_model` itself represents "a shape I don't recognise"
|
|
-- a shared-model decision affecting `server/` too, not a `client-core`-only
|
|
fix, so it is recorded here rather than silently worked around.
|
|
|
|
## What is not started at all
|
|
|
|
- **`TranscriptSource.kt`** -- the layer that decides whether a page comes
|
|
from the transcript cache or the server, and stitches the two. Needs
|
|
`transcript_cache.rs` and `api.rs`'s transcript-page method, both of
|
|
which exist now, so this is unblocked whenever picked up.
|
|
- **The markdown *block* model beyond syntax spans** -- `highlight/markdown.rs`
|
|
colours a `.md` file or fence for the highlighter, but does not build the
|
|
block tree (headings, lists, tables, fences as distinct nodes) that a
|
|
renderer walks to lay out prose versus code versus a table.
|
|
`CodeFence.kt`'s use of `org.intellij.markdown` for that full CommonMark
|
|
AST is Compose rendering plumbing, not something to port as-is; a Rust
|
|
UI layer will want its own block parser or a crate for it, decided
|
|
alongside the framework choice in RUST.md.
|
|
- **`TranscriptUnits.kt`** (see above) -- deliberately out of scope, since
|
|
it flattens a row into bounded units for a *specific* lazy-list
|
|
framework's composition cost, which is a fact about that framework
|
|
rather than about the transcript.
|
|
|
|
## Verifying
|
|
|
|
`./run-tests.sh` from the repo root now runs `event-model`, `client-core`
|
|
and `server` in that order (each `cargo test`, forwarding arguments the
|
|
same way it always has). From `client-core/` directly: `cargo test`,
|
|
`cargo clippy --all-targets`, `cargo fmt` -- all clean as of this writing.
|