feat(app): live session update + tool renderer + input_tokens migration
SessionDetail live update: - Extract session-view-state.mjs: capture scroll position, disclosure (open/skill-md-open) state, and visible-UUID anchor before refresh; reconcile messages by UUID (in-place update, append tail only); restore scroll and disclosure state after DOM patch. findLastMessageAtOrAbove uses binary search (O(log n)) instead of linear scan. - scrollRevision tracks user scrolls during refresh to avoid stale anchors overriding manual navigation. - Throttle onScroll to one rAF per frame. Tool renderer: - Extract tool-renderer.js: standalone module for rendering tool call cards (Read/Write/Edit diffs, Bash terminal output, search results, JS/TS syntax highlighting). Replaces inline rendering in SessionDetail. - tests/app-tool-renderer.test.mjs covers escaping, highlighting, and terminal formatting. Input tokens semantics migration: - Claude provider now sums input_tokens + cache_creation_input_tokens + cache_read_input_tokens into a single input_tokens value (was previously only the raw field, undercounting when cache tokens are present). - One-time index-wide re-parse triggered when the marker __claude_input_tokens_include_cache_v1__ is absent and the DB already has token data (self-healing on first build after upgrade). - App indexer.ts carries the same marker check for the app's build path. Also: - PRODUCT.md: product register (users, purpose, brand, design principles, accessibility targets). - README.md: minor wording updates. Co-Authored-By: Codex (GPT-5) <noreply@openai.com>
This commit is contained in:
@@ -27,6 +27,24 @@ function cursorToSkip(cursor: Cursor): number {
|
||||
}
|
||||
|
||||
export const name = 'claude';
|
||||
export const CLAUDE_INPUT_TOKEN_SEMANTICS_MARKER = '__claude_input_tokens_include_cache_v1__';
|
||||
|
||||
function totalInputTokens(usage: Record<string, unknown>): number | null {
|
||||
const fields = [
|
||||
'input_tokens',
|
||||
'cache_creation_input_tokens',
|
||||
'cache_read_input_tokens',
|
||||
];
|
||||
let seen = false;
|
||||
let total = 0;
|
||||
for (const field of fields) {
|
||||
const value = usage[field];
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue;
|
||||
seen = true;
|
||||
total += value;
|
||||
}
|
||||
return seen ? total : null;
|
||||
}
|
||||
|
||||
export function discover(_ctx: DiscoverContext): IndexUnit[] {
|
||||
return discoverJsonlFiles().map((f: any) => ({
|
||||
@@ -92,7 +110,7 @@ export function* parse(unit: IndexUnit, cursor: Cursor): Generator<IndexRecord,
|
||||
parent_uuid: obj.parentUuid || null, timestamp: ts, role: msg.role || obj.type,
|
||||
text, content_type: contentType, is_meta: (isMeta ? 1 : 0), model: msg.model || null,
|
||||
is_sidechain: obj.isSidechain ? 1 : 0, agent_id: aid,
|
||||
input_tokens: usage.input_tokens || null, output_tokens: usage.output_tokens || null,
|
||||
input_tokens: totalInputTokens(usage), output_tokens: usage.output_tokens || null,
|
||||
cwd: obj.cwd || null, skill: obj.attributionSkill || null, source: 'claude',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ export interface MessageRecord {
|
||||
model: string | null;
|
||||
is_sidechain: 0 | 1;
|
||||
agent_id: string | null;
|
||||
/** Provider-normalized total input, including provider-reported cached input. */
|
||||
input_tokens: number | null;
|
||||
output_tokens: number | null;
|
||||
cwd: string | null;
|
||||
|
||||
Reference in New Issue
Block a user