Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added
- Added `filled_text` to `cship.context_bar`, which fills the bar with successive characters of a string instead of repeating `filled_char`, so the bar can spell out a word or phrase as the context window fills (`CONTEXT░░░░░░░ 50%`). The bar is as wide as the text; zero-width characters such as combining accents join the character before them, and the track keeps the text's display width, so with a single-column `empty_char` wide characters don't make the bar grow as it fills. A `filled_text` with no visible characters is ignored with a warn-once diagnostic.
- Added `full_at` to `cship.context_bar`, the percentage at which the bar is drawn full (default `100`). Claude Code auto-compacts before the context window reaches 100%, so a bar scaled to 100% never completes; set `full_at` to your auto-compact point to fill it in time. Thresholds still compare the real percentage, and a `full_at` at or below 0 is ignored with a warn-once diagnostic.
- Added `filled_style` and `track_style` to `cship.context_bar`, which style the filled slots and the track on their own instead of sharing one style with the percentage. The threshold style then covers only the percentage, and each unset part style falls back to it. With neither set, the bar renders exactly as before.

## [1.8.3] - 2026-09-06

### Fixed
Expand Down
21 changes: 21 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ Renders a visual ASCII progress bar showing context window usage.
| `width` | `integer` | `10` | Number of characters in the bar |
| `filled_char` | `string` | `"█"` | Character used for the filled portion. Any Unicode character is allowed. |
| `empty_char` | `string` | `"░"` | Character used for the empty portion. Any Unicode character is allowed. |
| `filled_text` | `string` | — | Text revealed one character per slot as the bar fills, in place of `filled_char`. The bar is as wide as the text, so `width` is ignored. Zero-width characters such as combining accents join the character before them, and the track keeps the text's display width (with a single-column `empty_char`). Text with no visible characters is ignored. Emoji made of several characters (skin tones, flags, joined emoji) aren't supported. |
| `full_at` | `float` | `100` | % at which the bar is drawn full. Set it to your auto-compact point (e.g. `95`) to finish the bar before Claude Code auto-compacts. Thresholds still compare the real percentage. |
| `filled_style` | `string` | — | Style for the filled portion, in place of the threshold style |
| `track_style` | `string` | — | Style for the empty portion (the track), in place of the threshold style |
| `warn_threshold` | `float` | — | % at which style switches to `warn_style` |
| `warn_style` | `string` | `"yellow"` | Style at warn level |
| `critical_threshold` | `float` | — | % at which style switches to `critical_style` |
Expand All @@ -225,6 +229,23 @@ filled_char = "●"
empty_char = "○"
```

To reveal text as the context window fills (`CONTEXT░░░░░░░ 50%`), complete by 95%:

```toml
[cship.context_bar]
filled_text = "CONTEXT WINDOW"
full_at = 95
```

`filled_style` and `track_style` style the two parts of the bar on their own; the threshold style then covers only the percentage. Set them instead of styling `$value` in `format`: `$style` is left empty (`$symbol` takes the threshold style itself), and a style written directly in `format` doesn't reach the bar. For a bar whose text sits on a dark track:

```toml
[cship.context_bar]
filled_text = "CONTEXT WINDOW"
filled_style = "bold fg:#FFFFFF bg:#4C4C4C"
track_style = "fg:#929292 bg:#000000"
```

---

## `[cship.context_window]` — Context Window Details
Expand Down
16 changes: 16 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,19 @@ You can see the current cache state by running `cship explain` — it shows the
cship strips terminal control characters (ESC, BEL, the C0/C1 ranges, and DEL — including tab, carriage return, and newline) from untrusted session JSON fields such as `cwd`, `transcript_path`, `model`, and `workspace` before rendering. This is a security measure: it prevents a malicious directory or model name from injecting raw escape sequences that could spoof your terminal title, move the cursor, or write to the clipboard via OSC 52 ([CWE-150](https://cwe.mitre.org/data/definitions/150.html)).

Normal values are never affected — only control bytes are removed. If a path or name shows up with characters missing, it contained control bytes, which are stripped by design and cannot be disabled.

---

## How do I make the context bar spell out a word?

Set `filled_text` in `[cship.context_bar]`: the bar fills with successive characters of that text instead of repeating `filled_char`, and `full_at` sets the percentage at which it's complete — for example just before Claude Code auto-compacts. `full_at` only rescales the bar; `warn_threshold` and `critical_threshold` still compare the real percentage.

```toml
[cship.context_bar]
filled_text = "CONTEXT WINDOW" # CONTEXT░░░░░░░ at 50%
full_at = 95
filled_style = "bold"
track_style = "fg:#929292"
```

`filled_style` and `track_style` style the text and the track on their own, and the threshold style (`style`, `warn_style`, `critical_style`) then covers only the percentage. Style the bar through these fields rather than in a `format` string: with either set, `$style` is left empty (`$symbol` takes the threshold style itself), and a style written directly in `format` (e.g. `[$value](fg:#FFFFFF)`) doesn't reach the bar.
29 changes: 29 additions & 0 deletions docs/public/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,35 @@
"string",
"null"
]
},
"filled_text": {
"description": "Text revealed one character per slot as the bar fills, in place of `filled_char`.\nThe bar is as wide as the text, so `width` is ignored. Zero-width characters (such\nas combining accents) join the character before them, and the track keeps the\ntext's display width, so with a single-column `empty_char` the bar doesn't change\nwidth as it fills. Text with no visible characters is ignored. Emoji made of\nseveral characters (skin tones, flags, joined emoji like 👨‍👩‍👦) aren't supported:\nthey can reveal in pieces and shift the bar's width.\nExample: `\"CONTEXT WINDOW\"` renders `CONTEXT░░░░░░░ 50%` at 50%.",
"type": [
"string",
"null"
]
},
"full_at": {
"description": "Percentage at which the bar is drawn full. Defaults to `100`; set it lower to\nfinish the bar before Claude Code auto-compacts. It only rescales the bar:\nthresholds still compare the real percentage. Values at or below 0 are ignored.",
"type": [
"number",
"null"
],
"format": "double"
},
"filled_style": {
"description": "Style for the filled slots, in place of the threshold style. Setting this or\n`track_style` styles each part on its own, so the threshold style then covers\nonly the percentage. Style the bar through these fields rather than in `format`:\n`$style` is left empty (`$symbol` takes the threshold style itself), and a style\nwritten directly in `format` doesn't reach the bar.\nNot used when context data is absent; `empty_style` covers that state.",
"type": [
"string",
"null"
]
},
"track_style": {
"description": "Style for the empty slots (the track), in place of the threshold style.\nSee `filled_style`.",
"type": [
"string",
"null"
]
}
}
},
Expand Down
6 changes: 6 additions & 0 deletions src/ansi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ pub fn apply_style(content: &str, style_str: Option<&str>) -> String {
style.paint(content).to_string()
}

/// Prefix `content` with an SGR reset, so a style opened before it (e.g. by a literal span
/// in a `format` string) doesn't carry into content that styles its own parts.
pub fn after_reset(content: &str) -> String {
format!("\x1b[0m{content}")
}

/// Apply style with optional numeric threshold switching.
/// If `value` >= `critical_threshold` (both Some), applies `critical_style`.
/// If `value` >= `warn_threshold` (both Some), applies `warn_style`.
Expand Down
23 changes: 23 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,29 @@ pub struct ContextBarConfig {
/// Character used for empty (unused) slots. Defaults to `"░"`.
/// Example: `"○"` for hollow circles.
pub empty_char: Option<String>,
/// Text revealed one character per slot as the bar fills, in place of `filled_char`.
/// The bar is as wide as the text, so `width` is ignored. Zero-width characters (such
/// as combining accents) join the character before them, and the track keeps the
/// text's display width, so with a single-column `empty_char` the bar doesn't change
/// width as it fills. Text with no visible characters is ignored. Emoji made of
/// several characters (skin tones, flags, joined emoji like 👨‍👩‍👦) aren't supported:
/// they can reveal in pieces and shift the bar's width.
/// Example: `"CONTEXT WINDOW"` renders `CONTEXT░░░░░░░ 50%` at 50%.
pub filled_text: Option<String>,
/// Percentage at which the bar is drawn full. Defaults to `100`; set it lower to
/// finish the bar before Claude Code auto-compacts. It only rescales the bar:
/// thresholds still compare the real percentage. Values at or below 0 are ignored.
pub full_at: Option<f64>,
/// Style for the filled slots, in place of the threshold style. Setting this or
/// `track_style` styles each part on its own, so the threshold style then covers
/// only the percentage. Style the bar through these fields rather than in `format`:
/// `$style` is left empty (`$symbol` takes the threshold style itself), and a style
/// written directly in `format` doesn't reach the bar.
/// Not used when context data is absent; `empty_style` covers that state.
pub filled_style: Option<String>,
/// Style for the empty slots (the track), in place of the threshold style.
/// See `filled_style`.
pub track_style: Option<String>,
}

/// Configuration for `[cship.context_window]` sub-field modules.
Expand Down
Loading
Loading