Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
06aef1e
fix(tool): truncate_line byte-vs-char cap confusion
hakula139 Apr 24, 2026
cfa9141
fix(config/oauth): return Option from now_millis instead of panicking
hakula139 Apr 24, 2026
9732b25
fix(session): enforce truncate_title precondition
hakula139 Apr 24, 2026
10a3fb0
fix(tui/app): surface agent-channel closure instead of wedging UI
hakula139 Apr 24, 2026
621b85a
refactor(agent): move pending_calls from tui to agent module
hakula139 Apr 24, 2026
96db440
chore(gitignore): exclude temporary agent worktree directories
hakula139 Apr 24, 2026
c86fa7f
style: narrow pub to pub(crate) in binary-only crate (config, message…
hakula139 Apr 24, 2026
ae57211
test: rename mechanism-form test names to scenario form
hakula139 Apr 24, 2026
899b04b
docs(client): promote system-section assembly list to rustdoc
hakula139 Apr 24, 2026
1570b6f
refactor(client): collapse ContextEdit enum-of-one into tagged struct
hakula139 Apr 24, 2026
da5f626
perf(client): drop to_lowercase allocation from model-family checks
hakula139 Apr 24, 2026
71c9fc2
feat(client): add status-aware error messages for API failures
hakula139 Apr 24, 2026
621a7c2
test(client): tighten stream_message test, drop dead Unknown match arm
hakula139 Apr 24, 2026
ec49832
refactor(tool): extract bytes_to_mb to one suppression site
hakula139 Apr 24, 2026
e90f2f0
refactor(tool/edit): return Cow from normalize_eol to skip needless c…
hakula139 Apr 24, 2026
92229ec
test(tool/bash): tighten truncate_output bounds and multibyte assertions
hakula139 Apr 24, 2026
5877921
test(session/writer): parse recorded entry instead of substring matching
hakula139 Apr 24, 2026
b3307d0
style: narrow pub to pub(crate) across client module
hakula139 Apr 24, 2026
a6b6d72
style: add missing blank lines before section dividers in test modules
hakula139 Apr 24, 2026
3ea6557
test: tighten wrap and status assertions
hakula139 Apr 24, 2026
9f376c3
docs(CLAUDE): refresh crate tree and clarify four ambiguous rules
hakula139 Apr 24, 2026
281835b
test(tui/wrap): derive expected chunks from input to satisfy cspell
hakula139 Apr 24, 2026
402b175
docs(CLAUDE): PR descriptions must not reference gitignored working docs
hakula139 Apr 24, 2026
29e8b02
test(tui,session): cover dispatch error branches, drop unreachable pa…
hakula139 Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
.claude/agent-memory-local/
.claude/plans/
.claude/settings.local.json
.claude/worktrees/
14 changes: 8 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ ox # Start an interactive session
.
├── agent.rs # Agent turn loop, stream accumulation, tool dispatch
├── agent/
│ └── event.rs # AgentEvent, UserAction, AgentSink trait, StdioSink
│ ├── event.rs # AgentEvent, UserAction, AgentSink trait, StdioSink
│ └── pending_calls.rs # PendingCall / PendingCalls correlation state shared by live streaming and transcript resume
├── client.rs # Client module root
├── client/
│ ├── anthropic.rs # Anthropic Messages API streaming client
Expand Down Expand Up @@ -121,8 +122,8 @@ ox # Start an interactive session

### Blank Lines

- One blank line between top-level items (functions, structs, enums, impls, constants).
- One blank line before and after section dividers (`// ── Name ──`).
- One blank line between top-level items (functions, structs, enums, impls, constants). Exception: runs of closely-related one-line `const` / `static` declarations that share a theme (e.g., all the OAuth client constants, all the beta-header names) may sit together without blanks, then take one blank before unrelated items.
- One blank line before and after section dividers (`// ── Name ──`). This applies inside `#[cfg(test)]` modules too — the first divider takes a blank line after the `use super::*;` block.
- Inside function bodies, use blank lines to separate logical phases (e.g., setup → validation → execution → result).
- Group a single-line computation with its immediate validation guard (early-return `if`) — no blank between them. Multi-line `let` bindings (async chains, builder patterns) keep the blank before their guard.

Expand All @@ -132,7 +133,7 @@ ox # Start an interactive session
- Keep files focused: one primary type or concern per file. When a file or function grows large, split it into smaller units proactively rather than letting it accumulate.
- Place functions and types in the module that reflects their conceptual domain — import paths should not mislead about what the item does. Create new modules when needed for clean organization.
- Avoid `pub use` re-exports that obscure where items are defined. Prefer consistent import paths — if some items are re-exported, re-export all related items so callers never mix paths.
- Order helper functions after their caller (top-down reading order).
- Order helper functions after their caller (top-down reading order) _within each section_. Whole trait impls or unrelated feature sections don't need to be reshuffled to satisfy this — the rule is about local readability, not cross-section call graphs.
- When adding new fields to structs or variants to enums, place them at the most semantically appropriate position among existing members, not simply appended at the bottom.
- A type used by N callers across M modules belongs in the module that names the **contract**, not the module of the first **implementation**. If `tui::event::AgentSink` is implemented by both a TUI channel and a stdio writer, the trait belongs in `agent::` (the contract), not `tui::` (one implementation).

Expand All @@ -143,7 +144,7 @@ ox # Start an interactive session

### Imports

- Group `use` statements in three blocks separated by blank lines: std → external crates → internal modules.
- Group `use` statements in three blocks separated by blank lines: std → external crates → internal modules. `super::` and `crate::` paths belong together in the internal block — do not split them.
- Within each block, sort alphabetically.

### String Literals
Expand Down Expand Up @@ -180,13 +181,14 @@ ox # Start an interactive session
- Prose intro summarizing what and why.
- Per-file Changes table (for non-trivial PRs).
- Test plan checklist.
- PR descriptions are review-facing and must not reference gitignored working docs (e.g., `.claude/plans/*`, `.claude/agent-memory-local/*`). Those are internal collaboration notes, not reader context. When deferring follow-ups, describe them inline in the PR body — a reader should not need a file they can't see to understand the PR.

### Testing

- Unit tests in the same file as the code they test (`#[cfg(test)]` module).
- Integration tests in `tests/` directory for cross-module behavior.
- Group tests by function under `// ── function_name ──` section headers. Section order must mirror the production function order in the same file. Within each section, order: happy path → variants → edge / error cases.
- Name tests after the scenario they cover, not the return type. Prefix with the function name being tested (e.g., `parse_sse_frame_missing_data`, `load_oauth_expired_token`). For parameterless single-behavior functions where the value IS the test, use property form (`icon_is_dollar_sign`), not mechanism form (`icon_returns_dollar_sign`).
- Name tests after the scenario they cover, not the return type. Prefix with the function name being tested (e.g., `parse_sse_frame_missing_data`, `load_oauth_expired_token`). When the scenario and the return value are synonyms (unset env var → `None`), phrase the scenario side (`string_unset_is_absent`), not the mechanism (`string_unset_returns_none`). For parameterless single-behavior functions where the value IS the test, use property form (`icon_is_dollar_sign`), not mechanism form (`icon_returns_dollar_sign`).
- Use `indoc!` for multi-line string literals in tests.
- Reach for the established test infrastructure before hand-rolling: `wiremock` for HTTP round-trips, `temp-env` for environment-variable isolation, `ratatui::backend::TestBackend` + `insta` for TUI render snapshots (review with `cargo insta review`), and an extracted trait with an in-process fake (see `agent::AgentClient`) when a dependency is hard to mock at the network boundary.
- Write assertions that verify actual behavior, not just surface properties. Avoid uniform test data that makes `starts_with` / `ends_with` unfalsifiable, wildcard struct matches (`..`) that discard field values, and loose bounds that accept nearly any output. Each assertion should fail if the code under test has a plausible bug.
Expand Down
2 changes: 2 additions & 0 deletions crates/oxide-code/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! cap [`MAX_TOOL_ROUNDS`] trips.

pub(crate) mod event;
pub(crate) mod pending_calls;

use anyhow::{Context, Result, bail};
use tokio::sync::{Mutex, mpsc};
Expand Down Expand Up @@ -770,6 +771,7 @@ data: {"type":"message_stop"}
assert_eq!(messages.len(), 2);
assert!(matches!(&messages[1].content[0], ContentBlock::Text { text } if text == "hello"),);
}

// ── BlockAccumulator::into_content_block ──

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxide-code/src/agent/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub(crate) enum AgentEvent {
/// A chunk of thinking text (streamed incrementally).
ThinkingToken(String),
/// A tool call has started execution. `id` is the call's
/// correlation handle — [`PendingCalls`](crate::tui::pending_calls::PendingCalls)
/// correlation handle — [`PendingCalls`](crate::agent::pending_calls::PendingCalls)
/// stashes the tool name + input under it so the paired
/// [`Self::ToolCallEnd`] can build a structured
/// [`ToolResultView`](crate::tool::ToolResultView).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Pending tool-call bookkeeping shared between live streaming and
//! transcript resume.
//!
//! Both the live event path ([`super::app::App::handle_agent_event`])
//! and the resumed-history walk
//! ([`super::components::chat::ChatView::load_history`]) need to
//! bridge a tool-call observation to its later matching result so they
//! can render the result with:
//! Both the live event path
//! ([`crate::tui::app::App::handle_agent_event`]) and the resumed-history
//! walk ([`crate::tui::components::chat::ChatView::load_history`]) need
//! to bridge a tool-call observation to its later matching result so
//! they can render the result with:
//!
//! - The call's label as the status-line fallback when the tool
//! emits `title: None`.
Expand Down Expand Up @@ -172,7 +172,7 @@ mod tests {
}

#[test]
fn remove_unknown_id_returns_none() {
fn remove_unknown_id_is_absent() {
let mut calls = PendingCalls::new();
assert!(calls.remove("orphan").is_none());
}
Expand Down
2 changes: 1 addition & 1 deletion crates/oxide-code/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Anthropic Messages API client.

pub mod anthropic;
pub(crate) mod anthropic;
mod billing;
Loading