set_theme_override now updates the runtime theme immediately instead
of only setting the OnceLock, enabling live switching from /theme.
Replace hardcoded ~/.codex/themes references with resolved
$CODEX_HOME/themes paths in warnings, schema docs, and picker subtitle.
Extract selected_actual_idx() helper and guard move_up/move_down so
the callback is skipped when wrapping doesn't change the selected
item (e.g. single-item lists). Adds a test for the single-item case.
Highlight each hunk as a single block instead of per-line so that
multiline constructs (e.g. strings spanning multiple lines) get
correct syntax coloring. Adds a regression test.
Add a `/theme` slash command that opens an interactive picker listing
all bundled and custom (.tmTheme) syntax themes. The picker shows a
live-preview code snippet below the list that re-highlights as the
user navigates, and restores the original theme on cancel.
Key changes:
- ListSelectionView gains `footer_content`, `on_selection_changed`,
and `on_cancel` to support rich preview and lifecycle callbacks
- highlight.rs: swap global Theme from OnceLock to RwLock for live
swapping; add resolve/list/set/current helpers for theme management
- New theme_picker module with ThemePreviewRenderable and picker builder
- diff_render: make DiffLineType and helpers pub(crate); fix rename
highlighting to use destination extension
- markdown_render: fix CRLF double-newlines and info-string metadata
parsing for fenced code blocks
`set_theme_override` was called with `initial_config.tui_theme` before
onboarding/resume/fork could reload config. Because the theme state
lives in a `OnceLock`, it was permanently locked to the initial value.
Move the call to after the last possible config reload so the theme
and its warning always reflect the final session config.
- Tabs in syntax-highlighted diff spans now count as 4 columns instead
of zero, so tab-indented code wraps correctly in the diff view.
- Move theme warning injection to after the last possible config reload
(session resume/fork) so the ⚠ banner is not silently discarded.
Two fixes from code review:
1. `trim_end_matches('\n')` left a stray `\r` on CRLF inputs, which
propagated into rendered code blocks and diffs. Now strips both
`\r` and `\n`.
2. Unified diff highlighting called `highlight_code_to_styled_spans`
per line, bypassing the global size guardrails (max bytes/lines).
On large patches this triggered thousands of parser initializations.
Added a pre-check on aggregate patch size that skips highlighting
when limits are exceeded.
Extend theme resolution so `theme = "foo"` checks
~/.codex/themes/foo.tmTheme before falling back to auto-detection.
Surface unresolved theme names as a ⚠ startup warning in the TUI
via the existing startup_warnings pipeline.
Two fixes:
1. highlight.rs: The fallback path used split('\n') which produces a
phantom empty trailing element for inputs ending in '\n' (as
pulldown-cmark emits for code blocks). Switch to lines() which
handles trailing newlines correctly.
2. diff_render.rs: wrap_styled_spans only flushed the current line
when col >= max_cols AND the current span had remaining content.
This missed the case where one span ends exactly at max_cols and
the next span has content — the next span's first character was
appended to the already-full line. Remove the !remaining.is_empty()
guard so the line flushes at span boundaries too.
trim_end_matches('\n') stripped all trailing newlines from the code
block buffer before highlighting, discarding intentional blank lines
authored inside fences. pulldown-cmark appends exactly one trailing
'\n' to the text content, and LinesWithEndings handles it correctly
as a line terminator, so no trimming is needed. Remove the trim to
preserve user-authored blank lines faithfully.
Add `[tui] theme = "..."` config option that lets users override the
auto-detected syntax highlighting theme with any of the 32 bundled
two-face themes by kebab-case name. Invalid names log a warning and
fall back to adaptive detection.
Also suppress FontStyle::UNDERLINE in convert_style — themes like
Dracula mark type scopes with underline which produces distracting
underlines on type/module names in terminal output.
two-face's ~250 language syntax set resolves almost all names and
extensions on its own. Remove the redundant normalize_lang layer and
keep only 3 patched aliases (golang, python3, shell) that two-face
cannot resolve. This enables highlighting for any language in bat's
syntax set (elixir, haskell, scala, dart, php, html, css, etc.)
without needing explicit entries.
Replace syntect's default ~40 language set with two-face's bat-sourced
~250 language set, adding support for TypeScript, TSX, Kotlin, Swift,
and Zig. Auto-detect terminal background to pick CatppuccinMocha (dark)
or CatppuccinLatte (light). Suppress italic modifier by default since
many terminals render it poorly.
Integrate syntax highlighting into diff rendering and markdown code
blocks, with proper line wrapping that preserves styled spans.
- Add detect_lang_for_path() for extension-based language detection
- Syntax-aware diff rendering for Add/Delete/Update file changes
- wrap_styled_spans() splits long highlighted lines across display rows
- Buffer fenced code blocks in markdown_render for batch highlighting
- Add snapshot tests for highlighted diff wrapping
- Replace code_block_unhighlighted test with 3 syntax color assertions
Fixes#11852
Resume replay was applying transient runtime events (`TurnStarted`,
`StreamError`) as if they were live, which could leave the TUI stuck in
a stale `Working` / `Reconnecting...` state after resuming an
interrupted reconnect.
This change makes replay transcript-oriented for these events by:
- skipping retry-status restoration for replayed non-stream events
- ignoring replayed `TurnStarted` for task-running state
- ignoring replayed `StreamError` for reconnect/status UI
Also adds TUI regression tests and snapshot coverage for the interrupted
reconnect replay case.
## Why
`codex-core` was carrying the embedded system-skill sample assets (and a
`build.rs` that walks those files to register rerun triggers). Those
assets change infrequently, but any change under `codex-core` still ties
them to `codex-core`'s build/cache lifecycle.
This change moves the embedded system-skills packaging into a dedicated
`codex-skills` crate so it can be cached independently. That reduces
unnecessary invalidation/rebuild pressure on `codex-core` when the
skills bundle is the only thing that changes.
## What Changed
- Added a new `codex-rs/skills` crate (`codex-skills`) with:
- `Cargo.toml`
- `BUILD.bazel`
- `build.rs` to track skill asset file changes for Cargo rebuilds
- `src/lib.rs` containing the embedded system-skills install/cache logic
previously in `codex-core`
- Moved the embedded sample skill assets from
`codex-rs/core/src/skills/assets/samples` to
`codex-rs/skills/src/assets/samples`.
- Updated `codex-rs/core/Cargo.toml` to depend on `codex-skills` and
removed `codex-core`'s direct `include_dir` dependency.
- Removed `codex-core`'s `build.rs`.
- Replaced `codex-rs/core/src/skills/system.rs` implementation with a
thin re-export wrapper to keep existing `codex-core` call sites
unchanged.
- Updated workspace manifests/lockfile (`codex-rs/Cargo.toml`,
`codex-rs/Cargo.lock`) for the new crate.
## Why
`codex-rs/arg0` only needed two things from `codex-core`:
- the `find_codex_home()` wrapper
- the special argv flag used for the internal `apply_patch`
self-invocation path
That made `codex-arg0` depend on `codex-core` for a very small surface
area. This change removes that dependency edge and moves the shared
`apply_patch` invocation flag to a more natural boundary
(`codex-apply-patch`) while keeping the contract explicitly documented.
## What Changed
- Moved the internal `apply_patch` argv[1] flag constant out of
`codex-core` and into `codex-apply-patch`.
- Renamed the constant to `CODEX_CORE_APPLY_PATCH_ARG1` and documented
that it is part of the Codex core process-invocation contract (even
though it now lives in `codex-apply-patch`).
- Updated `arg0`, the core apply-patch runtime, and the `codex-exec`
apply-patch test to import the constant from `codex-apply-patch`.
- Updated `codex-rs/arg0` to call
`codex_utils_home_dir::find_codex_home()` directly instead of
`codex_core::config::find_codex_home()`.
- Removed the `codex-core` dependency from `codex-rs/arg0` and added the
needed direct dependency on `codex-utils-home-dir`.
- Added `codex-apply-patch` as a dev-dependency for `codex-rs/exec`
tests (the apply-patch test now imports the moved constant directly).
## Verification
- `cargo test -p codex-apply-patch`
- `cargo test -p codex-arg0`
- `cargo test -p codex-core --lib apply_patch`
- `cargo test -p codex-exec
test_standalone_exec_cli_can_use_apply_patch`
- `cargo shear`
## Why
`codex-rs/core/src/lib.rs` re-exported a broad set of types and modules
from `codex-protocol` and `codex-shell-command`. That made it easy for
workspace crates to import those APIs through `codex-core`, which in
turn hides dependency edges and makes it harder to reduce compile-time
coupling over time.
This change removes those public re-exports so call sites must import
from the source crates directly. Even when a crate still depends on
`codex-core` today, this makes dependency boundaries explicit and
unblocks future work to drop `codex-core` dependencies where possible.
## What Changed
- Removed public re-exports from `codex-rs/core/src/lib.rs` for:
- `codex_protocol::protocol` and related protocol/model types (including
`InitialHistory`)
- `codex_protocol::config_types` (`protocol_config_types`)
- `codex_shell_command::{bash, is_dangerous_command, is_safe_command,
parse_command, powershell}`
- Migrated workspace Rust call sites to import directly from:
- `codex_protocol::protocol`
- `codex_protocol::config_types`
- `codex_protocol::models`
- `codex_shell_command`
- Added explicit `Cargo.toml` dependencies (`codex-protocol` /
`codex-shell-command`) in crates that now import those crates directly.
- Kept `codex-core` internal modules compiling by using `pub(crate)`
aliases in `core/src/lib.rs` (internal-only, not part of the public
API).
- Updated the two utility crates that can already drop a `codex-core`
dependency edge entirely:
- `codex-utils-approval-presets`
- `codex-utils-cli`
## Verification
- `cargo test -p codex-utils-approval-presets`
- `cargo test -p codex-utils-cli`
- `cargo check --workspace --all-targets`
- `just clippy`
## Why
Compiling `codex-rs/core` is a bottleneck for local iteration, so this
change continues the ongoing extraction of config-related functionality
out of `codex-core` and into `codex-config`.
The goal is not just to move code, but to reduce `codex-core` ownership
and indirection so more code depends on `codex-config` directly.
## What Changed
- Moved config diagnostics logic from
`core/src/config_loader/diagnostics.rs` into
`config/src/diagnostics.rs`.
- Updated `codex-core` to use `codex-config` diagnostics types/functions
directly where possible.
- Removed the `core/src/config_loader/diagnostics.rs` shim module
entirely; the remaining `ConfigToml`-specific calls are in
`core/src/config_loader/mod.rs`.
- Moved `CONFIG_TOML_FILE` into `codex-config` and updated existing
references to use `codex_config::CONFIG_TOML_FILE` directly.
- Added a direct `codex-config` dependency to `codex-cli` for its
`CONFIG_TOML_FILE` use.
## Summary
- move regular-turn context diff/full-context persistence into
`run_turn` so pre-turn compaction runs before incoming context updates
are recorded
- after successful pre-turn compaction, rely on a cleared
`reference_context_item` to trigger full context reinjection on the
follow-up regular turn (manual `/compact` keeps replacement history
summary-only and also clears the baseline)
- preserve `<model_switch>` when full context is reinjected, and inject
it *before* the rest of the full-context items
- scope `reference_context_item` and `previous_model` to regular user
turns only so standalone tasks (`/compact`, shell, review, undo) cannot
suppress future reinjection or `<model_switch>` behavior
- make context-diff persistence + `reference_context_item` updates
explicit in the regular-turn path, with clearer docs/comments around the
invariant
- stop persisting local `/compact` `RolloutItem::TurnContext` snapshots
(only regular turns persist `TurnContextItem` now)
- simplify resume/fork previous-model/reference-baseline hydration by
looking up the last surviving turn context from rollout lifecycle
events, including rollback and compaction-crossing handling
- remove the legacy fallback that guessed from bare `TurnContext`
rollouts without lifecycle events
- update compaction/remote-compaction/model-visible snapshots and
compact test assertions (including remote compaction mock response
shape)
## Why
We were persisting incoming context items before spawning the regular
turn task, which let pre-turn compaction requests accidentally include
incoming context diffs without the new user message. Fixing that exposed
follow-on baseline issues around `/compact`, resume/fork, and standalone
tasks that could cause duplicate context injection or suppress
`<model_switch>` instructions.
This PR re-centers the invariants around regular turns:
- regular turns persist model-visible context diffs/full reinjection and
update the `reference_context_item`
- standalone tasks do not advance those regular-turn baselines
- compaction clears the baseline when replacement history may have
stripped the referenced context diffs
## Follow-ups (TODOs left in code)
- `TODO(ccunningham)`: fix rollback/backtracking baseline handling more
comprehensively
- `TODO(ccunningham)`: include pending incoming context items in
pre-turn compaction threshold estimation
- `TODO(ccunningham)`: inject updated personality spec alongside
`<model_switch>` so some model-switch paths can avoid forced full
reinjection
- `TODO(ccunningham)`: review task turn lifecycle
(`TurnStarted`/`TurnComplete`) behavior and emit task-start context
diffs for task types that should have them (excluding `/compact`)
## Validation
- `just fmt`
- CI should cover the updated compaction/resume/model-visible snapshot
expectations and rollout-hydration behavior
- I did **not** rerun the full local test suite after the latest
resume-lookup / rollout-persistence simplifications
## Why
Developers are frequently running low on disk space, and routine use of
`--all-features` contributes to larger Cargo build caches in `target/`
by compiling additional feature combinations.
This change updates local workflow guidance to avoid `--all-features` by
default and reserve it for cases where full feature coverage is
specifically needed.
## What Changed
- Updated `AGENTS.md` guidance for `codex-rs` to recommend `cargo test`
/ `just test` for full-suite local runs, and to call out the disk-usage
cost of routine `--all-features` usage.
- Updated the root `justfile` so `just fix` and `just clippy` no longer
pass `--all-features` by default.
- Updated `docs/install.md` to explicitly describe `cargo test
--all-features` as an optional heavier-weight run (more build time and
`target/` disk usage).
## Verification
- Confirmed the `justfile` parses and the recipes list successfully with
`just --list`.
## Summary
`gpt-5.3-codex` really likes to write complicated shell scripts, and
suggest a partial prefix_rule that wouldn't actually approve the
command. We should only show the `prefix_rule` suggestion from the model
if it would actually fully approve the command the user is seeing.
This will technically cause more instances of overly-specific
suggestions when we fallback, but I think the UX is clearer,
particularly when the model doesn't necessarily understand the current
limitations of execpolicy parsing.
## Testing
- [x] Add unit tests
- [x] Add integration tests