Skip to content

Redesign the context views with a tile grid - #555

Merged
sambitcreate merged 10 commits into
mainfrom
claude/project-thread-iymslo
Oct 19, 2026
Merged

sambitcreate merged 10 commits into
mainfrom
claude/project-thread-iymslo

Conversation

@sambitcreate

@sambitcreate sambitcreate commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Requested by sbt · project thread

Before: the Quick View Context card used a thin stacked bar, which at low usage became a row of colored slivers, followed by a dense legend table. The composer meter was a gauge icon with a popover listing text rows. The Context tool repeated those rows and added plain lists for Session and Recorded usage.

After: the three surfaces share one visual, picked from the mockups as option C, "Tiles". There are 100 tiles, each 1% of the model's context window. Used tiles are colored by category, followed by free space, with the hatched response reserve at the end.

  • Quick View card: a 20×5 strip of tiles, then a row per category showing tokens, % of window and a bar relative to the largest category. Below that are quiet Reserved, Free and deferred ("not counted") rows, and compaction state appears as a soft status pill.
  • Composer: the meter is 10 pips and turns amber at 80%. The popover shows a 10×10 grid beside the percentage and the term rows (conversation, system + tools, recent work, reserved, model context).
  • Context tool: three cards. Next request puts the square grid beside the bars. Session shows the title, provider and model chips, and dates. Recorded usage shows the token total, a "% from cache" pill and a usage bar split into cache read, input, cache write and output.
  • Prompt cache timer (Quick View card and Context tool): for models whose provider documents a fixed cache lifetime, a countdown shows how long the cache is guaranteed to stay warm after the last request, with a draining bar that turns amber in the last minute. When it ends it reads "Expired" and says the next request may need to rebuild the cache.

How: renderer/components/context-tiles.tsx holds the shared pieces. contextTiles() allocates the 100 tiles by largest remainder. Any used context or reserve always gets at least one tile, and an over-budget projection takes tiles from the reserve. ContextTiles renders a role="img" whose label names every segment, and ContextPips draws the pips. ContextBreakdown gains layout="panel" for the Context tool. All colors come from the existing --chart-* and status tokens, so light, dark and the theme presets all work. Every number still comes from the main-process projection; nothing is re-estimated in the renderer.

Cache timer: ChatContextPressureV1 gains an optional promptCache field ({ ttlMs, lastRequestAt }). The parser drops a malformed value without losing the projection.

  • Ambient projections take lastRequestAt from the newest assistant response on the same provider and model that read or wrote the cache (promptCacheWindow in generation-context.ts). They also use a later cache-warming refresh, which is remembered per chat (rememberPromptCacheRefresh), so stopping during a tool wait keeps the right expiry.
  • Live projections in llm-client.ts use the moment each request is prepared. A successful warming refresh re-sends the window from its dispatch time.
  • Contract scope: desktop only. Remote, iOS and Android don't consume it.

The lifetime comes from promptCacheTtlMs in main/services/prompt-cache-lifetimes.ts. It uses the catalog's promptCache.short first, which matches Aiden's default "short" retention. Otherwise it uses a sourced table of providers that publish a fixed idle lifetime that each cache read restarts (docs checked 2026-10-19). These lifetimes are guarantees, so a cache can outlive the countdown.

Provider and models Lifetime Source
Claude via Anthropic, Cloudflare, Vercel, OpenRouter or any endpoint sent Anthropic cache markers 5 min Anthropic
Bedrock: Claude models Pi adds cache points for, and Nova 5 min AWS
OpenAI and Azure, GPT-5.6 and later 30 min OpenAI, Azure
Moonshot Kimi K3 (K2.6 and K2.7 don't write caches) 5 min Kimi
MiniMax M2 series, explicit caching via the Anthropic API 5 min MiniMax
GitHub Copilot: Claude / OpenAI models 1 h / 24 h GitHub

These get no timer, because their docs give only a range or nothing:

  • OpenAI before GPT-5.6 (5–10 min, up to 1 h)
  • Codex
  • Gemini implicit caching
  • xAI
  • DeepSeek
  • Z.ai
  • Mistral
  • Groq (its page contradicts itself)
  • Fireworks
  • Kimi Coding
  • Kimi K2.6/K2.7
  • MiniMax M3, which only has automatic caching ("varies with load")

Cache warming still uses only the catalog's lifetime.

A review subagent checked the first commit. The second commit fixes what it found:

  • The popover now warns in the approaching phase, matching the pips.
  • Cache writes now count toward the cache share.
  • % of window, the window size, the estimate marker and the deferred-tools sentence are back.
  • A small reserve keeps one tile.
  • Pills now use the control radius instead of a one-off value.

Edge cases across providers (third commit)

Every case was rendered with the real components from projections shaped like each provider's. Light and dark screenshots are on the edge-case page, which is private to the owner.

Case Before After
Static prompt larger than the window (e.g. a local 2K model) "900000%" and "will compact", but compaction can't help ">999%" and a note to pick a larger-context model or turn off tool-heavy integrations
Little context in a 2M window (Gemini) "0%" with no pips lit while a tile is colored; categories at "0.0%" "<1%" with one pip lit; small categories show "<0.1%"
Provider counted more than Aiden itemized "Provider-counted, not itemized" truncated in the narrow card Shorter "Not itemized" label
Provider never reports usage (Ollama and similar) Seven rows of "Unavailable" One line, and the Next request card no longer stretches
Long sessions of a billion tokens or more "1200.0M" "1.2B"; rounding edges read "1M" / "100K", not "1000.0K"

These already behaved well: over budget at 128K, Claude at 1M, approaching the limit with estimates, compacting, local 8K without a breakdown, a large response reserve, and the narrow Context tab.

The fourth commit fixes a Pullfrog finding. At the 18px UI font size, the composer popover clipped token values. The popover width now scales with --ui-font-size, and each row can wrap its value instead of clipping it. Pullfrog re-checked fonts from 12–18px and confirmed the fix.

Later commits add the per-provider lifetimes and fix review findings from Pullfrog and Hermes:

  • Warming refreshes restart the displayed window, both live and after Stop.
  • The countdown interval stops at expiry.
  • The countdown re-reads the clock when a new window arrives.
  • Kimi and MiniMax are limited to the documented models.
  • The post-expiry wording is softer.
  • The new suite is registered in the CI test registry.

Not changed, noted for later:

  • Agent harness chats (Antigravity over ACP) still show Aiden's own projection, which may not match the agent's real context.
  • Bot instructions and project instructions share --chart-7.
  • Providers whose docs give only a range get no timer rather than a guessed one.

Tests: context-breakdown.test.tsx, context-meter.test.tsx, context-pressure.test.ts, generation-context.test.ts and pi-cache-warming.test.ts are updated and extended. prompt-cache-lifetimes.test.ts is new and registered in package.json and scripts/ci-test-registry.json.

  • Earlier commits: tile layout and ordering, the largest-remainder bounds, one tile for a tiny context or reserve, the over-budget reserve, the pip counts, the approaching-phase popover and the cache-share pill.
  • Edge-case commit: the too-small window, "<1%" / "<0.1%", billions, rounding edges and the no-usage line.
  • Cache timer: the countdown and expired states, and hour-long countdowns ("23:58:30 left", "Kept for 24 h").
  • Cache window: the parser keeping or dropping the window, which request starts it, and later, older and other-model warming refreshes.
  • Lifetime resolver: checked against the shipped Pi catalog entries for every provider above, including those that get none.
  • Warming: only a completed refresh restarts the window, measured from its dispatch.

Locally, the touched suites, test:model-catalog and ci-test-registry.test.mjs pass, and tsc and eslint are clean. The e2e selectors (Context usage,…, "Recorded usage", "Send a message") are unchanged.

Flaky test record: Desktop tests (core-git) on macOS failed with 2 of 3,618 tests on 99736b2 (run) and passed on one re-run. On that commit the PR touched no code in that lane, which covers main-process git, worktree and service code. I couldn't name the two specs because the log tool only returns the last 5,000 lines. The b307062 lane failures were the unregistered suite, not flakes.

Docs: the Context row in docs/chatgpt-desktop-ui-inspiration.md and .memory/quick-view-cards.md are updated.


🤖 Generated with Claude Code

https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

The Quick View Context card, the composer meter and its popover, and the
Context tool now share one visual: 100 tiles over the whole context window,
each 1% of it, colored by category, then free space and the hatched
response reserve. Categories get their own bars, the composer and Quick
View summary use a 10-pip meter, and compaction state is a soft status
pill. The Context tool groups Next request, Session and Recorded usage
into cards with a cache-share usage bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7
The composer popover now warns from the approaching phase, matching its
pips and Quick View. The cache share counts cache writes as prompt input.
Category rows show % of window again, both layouts show used of usable
with the estimate marker, the caption and popover state the window size,
the deferred-tools sentence is back, a small reserve keeps one tile, and
pills use the control radius.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7
@sambitcreate sambitcreate self-assigned this Oct 19, 2026
@very-hermes-bot

very-hermes-bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Hermes Review Bot

Confidence: 5

Engine: agy/gemini-3.8-flash-high
Review mode: full
Head: 3fb2a6c07c12647c11813ae5b53e87e2fd1e0e55
Generated: 2026-10-19T22:44:08+00:00
Reviews: 1

Summary

Redesigns Aiden's context pressure presentation across the Quick View card, the composer meter, and the sidebar Context tool around a shared 100-tile grid and a 10-pip meter. Next-request projections allocate 100 tiles (1% of the model window each) by largest remainder across static and message categories, quiet free space, and response reserve, and the composer meter illuminates pips per 10% of usable budget with amber warning cues at 80%. A new prompt cache timer tracks documented fixed idle lifetimes (Claude, Bedrock, OpenAI/Azure GPT-5.6+, Kimi K3, MiniMax M2, and GitHub Copilot) based on the latest cache-active assistant turn and cache-warming refreshes. Maintainers should double-check that the newly documented model lifetime heuristics in main/services/prompt-cache-lifetimes.ts:37-62 remain aligned as upstream provider caching policies evolve over time.

Confidence Score: 5/5

Fully traced and verified across main process projections, IPC notifications, cache-warming callbacks, renderer components, accessibility markup, and test suites.

📁 Important Files Changed
  • renderer/components/context-tiles.tsx: Core visual primitives implementing the 100-tile Hamilton-Hare distribution (contextTiles), accessible ARIA label generation, 10-pip meter (ContextPips), and token/percentage formatting.
  • renderer/components/context-breakdown.tsx: Renders the full breakdown view across both card (Quick View) and panel (Context tool) layouts, including category progress bars, deferred MCP server disclosures, and the live countdown timer (PromptCacheTimer).
  • renderer/components/context-meter.tsx: Redesigns the composer's compact trigger with 10 pips and updates its popover to show a 10x10 tile grid alongside itemized token terms and compaction status notes.
  • renderer/components/chat-context-panel.tsx: Restructures the sidebar Context tool into responsive Next request, Session, and Recorded usage cards, adding cumulative billion-token scaling and a segmented usage bar with cache hit ratio.
  • main/services/prompt-cache-lifetimes.ts & main/services/prompt-cache-lifetimes.test.ts: Service resolving provider cache idle retention guarantees from catalog metadata and model ID heuristics, accompanied by a comprehensive test suite.
  • main/services/generation-context.ts & main/services/context-pressure.ts: Computes prompt cache window bounds from the transcript's newest cached assistant turn, extending it upon successful background cache-warming refreshes.
  • main/services/llm-client.ts: Propagates prompt cache windows during live generation projections and forwards cache-warming refresh timestamps.
  • renderer/shared/context-pressure.ts: Extends ChatContextPressureV1 with optional PromptCacheWindowV1 payload and hardens token formatting against rounding edge cases (e.g., 999,950 tokens).

Findings

No findings.

Sequence Diagram

sequenceDiagram
    participant LLM as Provider / LLM Stream
    participant Warmer as PiCacheWarmer
    participant Client as llmClient / Main
    participant Ctx as GenerationContext
    participant UI as Renderer (ContextBreakdown)

    Note over Client,UI: Live Generation & Cache Window Initialization
    Client->>Ctx: createGenerationHarness (promptCacheTtlMs)
    Client->>UI: chat:context-pressure (lastRequestAt = Date.now())
    UI->>UI: PromptCacheTimer starts countdown

    Note over Warmer,UI: Cache Warming Extension
    Warmer->>LLM: streamSimple (maxTokens: 1)
    LLM-->>Warmer: message (cacheRead > 0)
    Warmer->>Client: onCacheRefreshed(dispatchedAt)
    Client->>Client: rememberPromptCacheRefresh(chatId, refresh)
    Client->>UI: chat:context-pressure (lastRequestAt = dispatchedAt)
    UI->>UI: PromptCacheTimer resets expiry

    Note over Client,UI: Ambient Read After Stop / Idle
    UI->>Client: chats:contextPressure (chatId)
    Client->>Ctx: promptCacheWindow(model, messages, refresh)
    Ctx-->>Client: { ttlMs, lastRequestAt }
    Client-->>UI: ChatContextPressureV1 with promptCache
Loading
[]

Last reviewed commit: 3fb2a6c07c12
Reviews (1) · Comment /hermes review to trigger a new review · /hermes review full for full re-review

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The composer popover clips token values at the supported 18px UI font size. The inline finding includes a browser reproduction and screenshot.

Reviewed changes across the shared context visuals, Quick View, composer popover, Context tool, tests, and accompanying documentation.

  • Shared tiles and pips: Reviewed whole-window tile allocation, category ordering, provider-counted excess, response reserves, and usable-input pip counts.
  • Context breakdown: Reviewed category bars, percentages, deferred-tool disclosure, compaction status, and the new panel layout.
  • Composer popover: Reviewed warning parity, token details, accessible labels, and rendered layout across supported font sizes.
  • Session and usage cards: Reviewed session metadata, retained usage aggregation, the cache-share calculation, and the usage chart.
  • Tests and docs: Reviewed updated behavioral coverage and the descriptions in project memory and the UI guide.

Validation: 71 focused tests passed, as did TypeScript checking and ESLint on the changed TS/TSX files. Chromium checks used the actual components and app CSS, including light/dark mode and 12–18px fonts; full Electron E2E was not run locally on Linux.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread renderer/components/context-meter.tsx
- Read ">999%" and explain a too-small window instead of promising compaction
- Show "<1%" (with one pip lit) and "<0.1%" when nonzero context rounds to zero
- Format billions in recorded usage and avoid "1000.0K" rounding
- Collapse recorded usage to one line when the provider never reports it
- Shorten the provider-excess legend label so it fits the narrow card

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7
The popover width now scales with the UI font size, and each row can
wrap its value below the term instead of clipping it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues found in this delta. The existing popover-clipping finding remains unresolved, so this review does not approve the PR.

Reviewed changes since the prior review at be50c33b, covering the provider and context-window edge cases added in c34f803a.

  • Hardened small-window messaging to explain when the static prompt exceeds the usable budget instead of promising that compaction will help.
  • Preserved tiny usage visibility with <1% labels, a lit first pip for nonzero usage, and <0.1% category shares; capped extreme displayed percentages at >999%.
  • Shortened provider-counted excess to Not itemized throughout the category rows and tile labels.
  • Simplified missing usage reports to one explanatory message and stopped the Next request card from stretching to the adjacent column's height.
  • Improved token formatting at rounding boundaries and added billion-token cumulative totals, with extended rendering and formatting tests.

Validation: 85 focused tests passed, along with npx tsc --noEmit and ESLint on the changed TS/TSX files. Browser layout checks were not repeated; full Electron E2E was not run on Linux.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes since c34f803a, focused on the composer popover's large-font clipping fix in 99736b26.

  • Expanded popover sizing with the UI font size while retaining a viewport-width cap.
  • Enabled token-row wrapping so values remain readable when the available width cannot accommodate both the term and value on one line.

Validation: all 40 focused context and Quick View tests passed, along with npx tsc --noEmit and scoped ESLint. Chromium containment assertions using the actual components and app CSS passed at every UI font size from 12–18px, in light and dark modes, at 1280px, 800px, and 320px viewport widths. The previously reported clipping is fixed; its thread was already resolved. Full Electron E2E was not run on Linux.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

Copy link
Copy Markdown
Owner Author

Desktop tests (core-git) failed on 99736b2 with 2 failures out of 3,618 tests (macOS).

I don't think this PR caused it. The PR only changes renderer context components and their tests, and none of those files are in the core-git lane, which covers main-process services, git, worktrees and scripts. The lane passed in the unit job on be50c33, and the code it covers hasn't changed since. I'll re-run the failed job once when the workflow finishes, and record the failing tests here if it fails again.


Generated by Claude Code

Projections now carry the provider's prompt cache window when the model
declares a cache lifetime and a request has used the cache. Quick View
and the Context tool count it down and say when it has lapsed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new countdown can report an expired cache after a successful cache-warming request.

Reviewed changes since the prior review at 99736b26, focused on the prompt-cache countdown added in ad2808e8.

  • Added cache-window metadata derived from model lifetimes and cached assistant requests, plus live projection timestamps.
  • Added countdown displays to Quick View and the Context tool, with a draining bar, last-minute warning, and expired state.
  • Extended parsing, tests, and documentation for optional cache windows and countdown rendering.

Validation: 89 context and Quick View tests plus 15 cache-warming tests passed. TypeScript checking and scoped ESLint passed. Browser layout checks and full Electron E2E were not repeated.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread main/services/llm-client.ts Outdated

@very-hermes-bot very-hermes-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline review — 1 finding(s) anchored to the diff. See the pinned summary comment for the overview.

Comment on lines +149 to +154
React.useEffect(() => {
// Ticks against the wall clock until the cache lapses; nothing else drives this.
if (expiresAt <= Date.now()) return;
const timer = window.setInterval(() => setNow(Date.now()), 1000);
return () => window.clearInterval(timer);
}, [expiresAt]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1 · logic] Prompt cache countdown interval runs indefinitely after expiration
Confidence: 0.95

The interval created in useEffect is only prevented from starting if expiresAt has already passed upon setup, but once running, it never clears itself when current >= expiresAt, causing 1Hz state updates and component re-renders indefinitely on expired caches.

Repair: Check if current >= expiresAt within the interval callback and call window.clearInterval(timer) to stop ticking.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b307062: the interval now clears itself on the tick that reaches expiry.


Generated by Claude Code

The countdown now covers Claude through Anthropic markers or Bedrock, Bedrock
Nova, OpenAI and Azure GPT-5.6 and later, Kimi, MiniMax and GitHub Copilot,
from a sourced table used after the catalog's own lifetime. Providers that only
publish a range or nothing keep showing no timer.

Also addresses review: a successful cache-warming refresh re-sends the live
window from its dispatch, and the countdown interval stops once it expires.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new test registration blocks CI lane execution, and several provider lifetimes do not support the exact expiry shown by the countdown.

Reviewed changes since ad2808e8, focused on the provider lifetime resolver and cache-timer fixes in b3070626.

  • Added provider-specific lifetimes with catalog precedence, model/API checks, and catalog-backed tests.
  • Connected successful warming refreshes to live pressure notifications using the warming request's dispatch timestamp.
  • Stopped countdown intervals at expiry and added hour-based formatting for longer cache windows.
  • Updated test scripts and documentation for the expanded provider coverage.

Validation: 111 focused tests passed; TypeScript checking and scoped ESLint passed. CI registry validation failed 5 of 15 tests because main/services/prompt-cache-lifetimes.test.ts is unassigned. Primary provider documentation was checked; browser layout checks and Electron E2E were not repeated.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Comment thread package.json
Comment thread main/services/prompt-cache-lifetimes.ts Outdated
Comment on lines +51 to +54
if ((model.provider === "minimax" || model.provider === "minimax-cn") && model.api === "anthropic-messages") {
return 5 * MINUTE;
}
if (model.provider === "moonshotai" || model.provider === "moonshotai-cn") return 5 * MINUTE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Avoid five-minute expiry without a matching provider contract

The cited MiniMax documentation gives no cache lifetime, while the cited Kimi page documents the five-minute cache-write policy for kimi-k3, not the shipped K2.6/K2.7 entries. These branches nevertheless give those models a precise five-minute countdown followed by Expired, contrary to the stated policy of omitting undocumented lifetimes.

Technical details
# Unsupported five-minute provider fallbacks

## Affected sites
- `main/services/prompt-cache-lifetimes.ts:51-54` assigns five minutes across MiniMax and Moonshot provider families.
- `main/services/prompt-cache-lifetimes.test.ts:61-67` expects that duration for all shipped entries rather than distinguishing documented model policies.
- `renderer/components/context-breakdown.tsx:176-188` presents the value as remaining lifetime and then asserts expiration and rebuilding.

## Evidence
- https://platform.minimax.io/docs/api-reference/text-prompt-caching documents automatic prefix caching, usage fields, and minimum input size, but no five-minute TTL or renewal guarantee.
- https://platform.kimi.ai/docs/guide/use-context-caching-feature-of-kimi-api describes the 5m/1h cache-write policy for `kimi-k3` and explicitly says K2.6/K2.7 do not support Cache Write. The shipped Moonshot entries are K2.6/K2.7; that page does not establish their fixed idle lifetime.

## Required outcome
- Return a nonzero fallback only for models with a documented matching idle lifetime; otherwise omit the countdown.
- Align tests and provider-coverage documentation with the supported model policies.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Narrowed in 016ef0b.

  • Kimi: only kimi-k3 gets the 5 min timer. The page says K2.6 and K2.7 don't write caches.
  • MiniMax: M2-series models get 5 min. The page's Explicit Prompt Caching column says "5-minute expiration, automatically renewed with continued use" for those models. That is the mode Pi's cache_control markers on the Anthropic endpoint request. M3 only has automatic caching, which "varies with load", so it gets no timer.

Tests cover each model on both sides of the rule.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Kimi restriction is addressed, but the current MiniMax page still documents only automatic caching with no five-minute TTL or renewal guarantee, so the MiniMax part remains open.

Pullfrog  | View workflow run | via Pullfrog | Using gpt-6.1-sol | 𝕏

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule stays as it is. I fetched that page again today, and it has a comparison table with two columns. The "Prompt Caching (Passive)" column says "Expiration time automatically adjusted based on system load" for M3, M2.7, M2.5 and M2.1. The "Explicit Prompt Caching" column, for the M2.7, M2.5, M2.1 and M2 series, says "5-minute expiration, automatically renewed with continued use".

Pi sends cache_control markers to MiniMax's Anthropic endpoint, so M2-series requests use explicit caching. M3 is listed only under passive caching, so it gets no timer. If the table loads differently depending on locale or tab, the timer only shows when the request actually reads or writes the cache. That keeps the risk to an early "Expired".


Generated by Claude Code

Comment thread main/services/prompt-cache-lifetimes.ts

@very-hermes-bot very-hermes-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline review — 1 finding(s) anchored to the diff. See the pinned summary comment for the overview.

Comment on lines +154 to +165
const [now, setNow] = React.useState(() => initialNow ?? Date.now());
const remaining = Math.max(0, Math.min(cache.ttlMs, expiresAt - now));
React.useEffect(() => {
// Ticks against the wall clock until the cache lapses; nothing else drives this.
if (expiresAt <= Date.now()) return;
const timer = window.setInterval(() => {
const current = Date.now();
setNow(current);
if (current >= expiresAt) window.clearInterval(timer);
}, 1000);
return () => window.clearInterval(timer);
}, [expiresAt]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1 · logic] Stale state in PromptCacheTimer causes expired prompt caches to display as active indefinitely
Confidence: 0.95

Storing now in React state leaves it frozen after expiry or mount, causing an already-expired cache with expiresAt > now to compute a positive remaining time and display an active bar while the effect returns early without scheduling any interval to update it.

Repair: Compute now on each render from initialNow ?? Date.now() and drive 1-second re-renders using a tick counter in state until Date.now() >= expiresAt.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 016ef0b. When expiresAt changes, the effect now reads the clock again before deciding whether to tick, so a window that is already lapsed shows "Expired" straight away. initialNow pins the clock for static renders only.


Generated by Claude Code

Stopping during a tool wait after a warm settles without another foreground
request, so ambient reads now remember the chat's latest warming refresh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7
…models

Assigns prompt-cache-lifetimes.test.ts to a CI lane, limits the Kimi timer to
K3 and the MiniMax timer to M2-series explicit caching, says the next request
may need to rebuild an expired cache since lifetimes are guarantees, and
re-reads the clock when a new cache window arrives.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues found in this delta. The earlier CI-registry and provider-lifetime findings remain open, so this is not an approval.

Reviewed changes since the prior review at b3070626, focused on retaining cache-warming expiry through the transition from a stopped generation to ambient context projections.

  • Retained warming timestamps per chat and passed them into ambient cache-window reconstruction, with cleanup when the chat context is released.
  • Selected the newer matching refresh without overriding newer transcript usage or applying another model's timestamp.
  • Extended behavioral coverage and project memory for later, older, and other-model refreshes.

Validation: all 111 focused tests passed, along with npx tsc --noEmit and scoped ESLint. The CI registry suite still fails 5 of 15 tests with CI registry/source mismatch; unassigned=["main/services/prompt-cache-lifetimes.test.ts"], not-in-source=[], matching the existing finding. Browser layout checks and Electron E2E were not repeated.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

@very-hermes-bot very-hermes-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline review — 1 finding(s) anchored to the diff. See the pinned summary comment for the overview.

Comment on lines +154 to +165
const [now, setNow] = React.useState(() => initialNow ?? Date.now());
const remaining = Math.max(0, Math.min(cache.ttlMs, expiresAt - now));
React.useEffect(() => {
// Ticks against the wall clock until the cache lapses; nothing else drives this.
if (expiresAt <= Date.now()) return;
const timer = window.setInterval(() => {
const current = Date.now();
setNow(current);
if (current >= expiresAt) window.clearInterval(timer);
}, 1000);
return () => window.clearInterval(timer);
}, [expiresAt]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1 · logic] PromptCacheTimer retains stale now state across prop updates, causing expired caches to display as active
Confidence: 0.95

PromptCacheTimer stores now in useState and stops ticking upon expiration, so subsequent prop changes with an already-expired cache retain the stale now timestamp and render full remaining TTL indefinitely.

Repair: Evaluate now dynamically during render from initialNow ?? Date.now() and use a tick counter in useEffect to drive re-renders only while active.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finding was raised against 22b844b. It is already fixed in 016ef0b: the effect re-reads the clock whenever expiresAt changes, before it decides whether to tick, so a window that arrives already lapsed renders "Expired".


Generated by Claude Code

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues found in this delta. Two previously reported cache-lifetime concerns remain unresolved, so this is not an approval.

Reviewed changes since 22b844b2, focused on the lifetime restrictions, timer updates, and CI registration in 016ef0bf.

  • Narrowed provider fallback coverage to Kimi K3 and MiniMax M2-series models, with catalog-backed tests and corresponding documentation updates.
  • Refreshed the countdown clock when a new cache window arrives, including already-expired windows, and kept an explicit test clock deterministic.
  • Softened post-countdown wording so it no longer promises that the next request rebuilds the cache.
  • Assigned the lifetime test suite to a CI lane, fixing the registry validation failure.

Validation: all 111 focused context/cache tests and all 15 CI registry tests passed. Two additional temporary component checks passed for expired-window replacement, interval cleanup, and refresh after expiry. TypeScript checking and scoped ESLint passed. Primary provider documentation was rechecked; browser layout checks and Electron E2E were not repeated.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Provider lifetimes are guarantees, so the end of the countdown means the cache
may be gone rather than certainly gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues found in this delta. The earlier MiniMax lifetime finding remains open, so this is not an approval.

Reviewed changes since 016ef0bf, focused on the cache-status wording in 1c9c9505.

  • Changed lapsed-cache messaging from Expired to Likely expired, retaining the conditional rebuild caption instead of asserting that the cache is gone.
  • Updated rendering coverage to expect the qualified status in the shared context breakdown.

Validation: all 30 focused context-rendering tests passed, and ESLint passed for both changed files. Browser layout checks, TypeScript checking, and Electron E2E were not repeated for this wording-only delta.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues found in this delta. The previously reported MiniMax lifetime concern remains open, so this is not an approval.

Reviewed changes since 016ef0bf, focused on the lapsed-cache wording update in 1c9c9505.

  • Replaced the unconditional expiry label with Likely expired, retaining the conditional rebuild caption in both context views.
  • Updated the rendering assertion to cover the revised lapsed-cache state.

Validation: all 30 focused context rendering tests passed, and ESLint passed for the changed component and test. Browser layout checks and Electron E2E were not repeated for this wording-only change.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0192A2sztMG1oG9N6WJuYSX7

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues found in this delta. The previously reported MiniMax lifetime concern remains open, so this is not an approval.

Reviewed changes since 1c9c9505, focused on the integration of main in merge commit 3fb2a6c0.

  • Merged the serial test chain with main, retaining its test:workspace-artifacts command.
  • Preserved cache-lifetime test registration in the serial, compaction, coverage, and CI registry paths. The incremental diff contains no PR feature-code changes.

Validation: all 82 workspace-artifact tests and all 15 CI-registry tests passed. The script reconciliation removed no existing commands. Context rendering, browser layout, TypeScript, and Electron E2E checks were not repeated for this merge-only delta.

Pullfrog  | Fix it ➔ | View workflow run | Using gpt-6.1-sol | 𝕏

@sambitcreate
sambitcreate merged commit 1beb124 into main Oct 19, 2026
27 checks passed
@sambitcreate
sambitcreate deleted the claude/project-thread-iymslo branch October 19, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants