Skip to content

Add support for ai-title and prefer it over legacy summary#136

Merged
daaain merged 3 commits intomainfrom
feat/ai-title-support
May 9, 2026
Merged

Add support for ai-title and prefer it over legacy summary#136
daaain merged 3 commits intomainfrom
feat/ai-title-support

Conversation

@daaain
Copy link
Copy Markdown
Owner

@daaain daaain commented May 6, 2026

This needed a little migration, so also bumping required cache version.

To test locally, manually edit version in pyproject.yaml to 1.3.0 and generate with "just cli"

Summary by CodeRabbit

  • New Features

    • Sessions can show AI-generated short titles and will use them ahead of summaries or previews in lists, headers, and expanded views; these AI titles are hidden from normal message content.
  • Chores

    • Persistent storage and cache handling updated so AI titles are saved and respected across reloads and version transitions.
  • Tests

    • Added end-to-end and unit tests for AI-title parsing, deduplication, persistence, and cache/version compatibility.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds AI-generated session title support: new AiTitleTranscriptEntry type, DB migration and persistence, pipeline handling (load, filter, DAG skip, dedup last-wins), cache extraction/loading, renderer/TUI preference for ai_title, and tests covering parsing, dedup, priority, persistence, and version-compatibility.

Changes

AI-Generated Session Titles

Layer / File(s) Summary
Data Model
claude_code_log/models.py
MessageType gains AI_TITLE = "ai-title"; new AiTitleTranscriptEntry model added; TranscriptEntry union extended.
Factory
claude_code_log/factories/transcript_factory.py
Register "ai-title" in ENTRY_CREATORS to parse JSONL into AiTitleTranscriptEntry.
Loader / Filter / DAG / Dedup
claude_code_log/converter.py, claude_code_log/dag.py
load_transcript recognizes "ai-title"; filter_messages_by_date preserves ai-title entries without timestamps; build_message_index skips AiTitleTranscriptEntry (no UUID) in both passes; deduplicate ai-title per session with constant content key and last-wins; include ai-title in non-DAG re-append list.
Cache Build & Index Overlay
claude_code_log/converter.py
Extract last ai-title per session into session_ai_titles; exclude ai-title from normal grouping; populate SessionCacheData.ai_title; prefer ai_title in build_session_title and project indices (including archived).
Cache Persistence & Compatibility
claude_code_log/cache.py
SessionCacheData adds ai_title: Optional[str]; update_session_cache INSERT/ON CONFLICT persists/updates ai_title; get_cached_project_data and get_archived_sessions load ai_title; breaking_changes mapping extended ("1.2.0": "1.3.0").
SQL Migration
claude_code_log/migrations/006_session_ai_title.sql
Adds ai_title TEXT column with header comments describing JSONL payload and last-wins semantics.
Renderer & Template Generation
claude_code_log/renderer.py
New prepare_session_ai_titles extracts sessionId→aiTitle map; merge AI titles into summaries before template generation; skip ai-title entries in filtering and rendering.
TUI
claude_code_log/tui.py
Sessions table preview and expanded view prefer escaped session_data.ai_title over summary and preview; add Title section; replace custom escape helper with escape_markup.
Tests & Validation
test/test_ai_title.py, test/test_cache.py, test/test_html_regeneration.py
New tests for parsing, dedup (last-wins), build_session_title priority, and SQLite cache round-trip; cache and HTML tests updated for version-compatibility behavior.

Sequence Diagram

sequenceDiagram
  participant Parser
  participant Converter
  participant CacheManager
  participant SQLiteDB
  participant Renderer
  participant TUI

  Parser->>Converter: parse ai-title JSONL to AiTitleTranscriptEntry
  Converter->>Converter: preserve ai-title through filtering
  Converter->>Converter: deduplicate per session (last-wins)
  Converter->>CacheManager: extract session_ai_titles and build SessionCacheData
  CacheManager->>SQLiteDB: persist SessionCacheData including ai_title
  SQLiteDB->>CacheManager: return rows with ai_title
  CacheManager->>Renderer: provide SessionCacheData with ai_title
  Renderer->>Renderer: merge ai_title into session summaries
  Renderer->>TUI: pass session data with ai_title
  TUI->>TUI: display ai_title in table and expanded view
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • daaain/claude-code-log#65: Modifies message deduplication in converter.py and cache versioning in cache.py; shares structural overlap with this PR.

Poem

🐰 A title hopped in, polite and spry,

Last one wins, it caught the eye,
Saved in the cache, then shown with grace,
The TUI smiles and finds its place,
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding support for ai-title transcript entries and prioritizing them over legacy summary fields throughout the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ai-title-support

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@daaain daaain mentioned this pull request May 6, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
test/test_ai_title.py (1)

32-154: ⚡ Quick win

Add one cache round-trip test for ai_title.

These tests cover parse/dedup/title priority, but the feature also depends on the cache schema and save/load path. A tiny CacheManager.update_session_cache() → reload assertion would protect the migration and would catch persistence bugs in this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test_ai_title.py` around lines 32 - 154, Add a small test that exercises
the cache round-trip for ai_title: create or get a CacheManager, call
CacheManager.update_session_cache(session_id="s1", data=SessionCacheData(...,
ai_title="Saved AI title", ...)) (or the appropriate update API), then reload
the cache (either by creating a new CacheManager or calling the cache load
method) and assert the persisted SessionCacheData for "s1" contains ai_title ==
"Saved AI title" and that build_session_title("Project", "s1",
reloaded_cache_entry) returns "Project: Saved AI title"; use SessionCacheData
and build_session_title from the diff to construct/verify the entry so this will
catch save/load schema regressions.
claude_code_log/tui.py (1)

1746-1751: ⚡ Quick win

_escape_rich_markup duplicates the already-imported escape_markup; consolidate.

_escape_rich_markup (lines 1746–1751) does exactly what rich.markup.escape does — both escape [ and ]. Since escape_markup is already imported at line 24 and used in populate_table, the hand-rolled method is redundant. The new ai_title block in _update_expanded_content (and all pre-existing fields) could use the imported function instead.

♻️ Proposed refactor
-    def _escape_rich_markup(self, text: str) -> str:
-        """Escape Rich markup characters in text to prevent parsing errors."""
-        if not text:
-            return text
-        # Escape square brackets which are used for Rich markup
-        return text.replace("[", "\\[").replace("]", "\\]")
-
     def _update_expanded_content(self) -> None:
         ...
         # AI title (Claude Code's curated short title) - escape markup
         if session_data.ai_title:
-            escaped_title = self._escape_rich_markup(session_data.ai_title)
+            escaped_title = escape_markup(session_data.ai_title)
             content_parts.append(f"\n[bold]Title:[/bold] {escaped_title}")

         # Summary (if available) - escape markup
         if session_data.summary:
-            escaped_summary = self._escape_rich_markup(session_data.summary)
+            escaped_summary = escape_markup(session_data.summary)
             content_parts.append(f"\n[bold]Summary:[/bold] {escaped_summary}")

         # First user message - escape markup
         if session_data.first_user_message:
-            escaped_message = self._escape_rich_markup(session_data.first_user_message)
+            escaped_message = escape_markup(session_data.first_user_message)
             ...

         # Working directory (if available) - escape markup
         if session_data.cwd:
-            escaped_cwd = self._escape_rich_markup(session_data.cwd)
+            escaped_cwd = escape_markup(session_data.cwd)
             ...

Also applies to: 1773-1776

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@claude_code_log/tui.py` around lines 1746 - 1751, Remove the duplicate escape
helper and use the already-imported escape_markup from rich.markup: delete the
_escape_rich_markup method and replace its usages (e.g., in populate_table and
in _update_expanded_content where ai_title and other fields are escaped) with
calls to escape_markup(text). Ensure import of escape_markup remains and update
any references to _escape_rich_markup to call escape_markup so behavior is
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@claude_code_log/converter.py`:
- Around line 1081-1085: The pagination fallback in
_build_session_data_from_messages currently only fills ai_title via
session_ai_titles (from AiTitleTranscriptEntry) but does not reconstruct the
legacy summary, so paginated session titles skip the intended summary fallback;
update the pagination path (the same block that builds SessionCacheData) to
extract the legacy summary the same way
_update_cache_with_session_data()/_collect_project_sessions() do and pass that
summary into SessionCacheData so the title resolution order remains ai_title >
summary > preview > id (also apply the same fix to the analogous block around
lines 1163-1167).
- Around line 1751-1755: The ai_title write path is populating
SessionCacheData.ai_title and persisting immediately, but the INSERT in cache.py
(around the function handling cache refresh / session upserts) lists ai_title
twice and has fewer placeholders than bound values causing an exception; fix it
by editing the INSERT statement to include ai_title only once and adjust the
placeholders to match the exact number and order of bound SessionCacheData
fields, ensure the bound parameter list/order matches the INSERT column order,
and verify the converter.py logic that builds session_ai_titles (the loop
creating session_ai_titles and where SessionCacheData.ai_title is set) uses the
same field name so persistence and binding align.

---

Nitpick comments:
In `@claude_code_log/tui.py`:
- Around line 1746-1751: Remove the duplicate escape helper and use the
already-imported escape_markup from rich.markup: delete the _escape_rich_markup
method and replace its usages (e.g., in populate_table and in
_update_expanded_content where ai_title and other fields are escaped) with calls
to escape_markup(text). Ensure import of escape_markup remains and update any
references to _escape_rich_markup to call escape_markup so behavior is
unchanged.

In `@test/test_ai_title.py`:
- Around line 32-154: Add a small test that exercises the cache round-trip for
ai_title: create or get a CacheManager, call
CacheManager.update_session_cache(session_id="s1", data=SessionCacheData(...,
ai_title="Saved AI title", ...)) (or the appropriate update API), then reload
the cache (either by creating a new CacheManager or calling the cache load
method) and assert the persisted SessionCacheData for "s1" contains ai_title ==
"Saved AI title" and that build_session_title("Project", "s1",
reloaded_cache_entry) returns "Project: Saved AI title"; use SessionCacheData
and build_session_title from the diff to construct/verify the entry so this will
catch save/load schema regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e16de3f-aa38-43cf-aa6a-1284e4e6fd8c

📥 Commits

Reviewing files that changed from the base of the PR and between ece7f85 and 3140257.

📒 Files selected for processing (12)
  • claude_code_log/cache.py
  • claude_code_log/converter.py
  • claude_code_log/dag.py
  • claude_code_log/factories/transcript_factory.py
  • claude_code_log/migrations/006_session_ai_title.sql
  • claude_code_log/models.py
  • claude_code_log/renderer.py
  • claude_code_log/tui.py
  • test/test_ai_title.py
  • test/test_cache.py
  • test/test_dag_silent.py
  • test/test_html_regeneration.py

Comment thread claude_code_log/converter.py
Comment thread claude_code_log/converter.py
@daaain daaain force-pushed the feat/ai-title-support branch from 2d0d031 to 0aa9bdd Compare May 8, 2026 21:34
@daaain
Copy link
Copy Markdown
Owner Author

daaain commented May 8, 2026

Rebased this and fixed the conflicts, I think it's good to merge.

I've also been thinking we should cut a new release as there's been so many PRs getting merged lately!

@cboos
Copy link
Copy Markdown
Collaborator

cboos commented May 8, 2026

Rebased this and fixed the conflicts. I think it's good to merge.

Sorry for the churn ;-)

I've also been thinking we should cut a new release as there's been so many PRs getting merged lately!

Well, as I've started with "Monitor", I wanted to try to be complete with the other /loop related tools.
And maybe also EnterWorktree/ExitWorktree. With those, we should have a pretty up-to-date support of Claude Code again (until they add a ton more features again, of course ;-) ).

Speaking about this, have you tried Claude Code within Claude Desktop yet? Are the transcripts there compatible with the "regular" ones? There may be differences; we had a few with the Claude Code extension for Visual Code (it's been a while since the last time I've used it).

And then, there's CoWork, same question...

daaain added 2 commits May 9, 2026 01:04
# Conflicts:
#	claude_code_log/cache.py
#	claude_code_log/dag.py
@daaain daaain force-pushed the feat/ai-title-support branch from 1265bb6 to aaeab7a Compare May 9, 2026 00:04
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
claude_code_log/renderer.py (1)

946-959: ⚡ Quick win

Ignore blank ai-title values before overriding summaries.

If aiTitle is empty/whitespace, this can replace a usable legacy summary with an empty value and degrade session labels to UUID fallback. Filter out blank titles here.

Proposed patch
 def prepare_session_ai_titles(messages: list[TranscriptEntry]) -> dict[str, str]:
@@
     out: dict[str, str] = {}
     for message in messages:
         if isinstance(message, AiTitleTranscriptEntry):
-            out[message.sessionId] = message.aiTitle
+            title = message.aiTitle.strip()
+            if title:
+                out[message.sessionId] = title
     return out
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@claude_code_log/renderer.py` around lines 946 - 959, The
prepare_session_ai_titles function currently overwrites existing titles with
blank/whitespace aiTitle values; change the loop that handles
AiTitleTranscriptEntry so it only assigns out[message.sessionId] =
message.aiTitle when message.aiTitle.strip() is non-empty (i.e., ignore
blank/whitespace titles) so legacy summaries aren't replaced by empty strings;
reference the prepare_session_ai_titles function and the
AiTitleTranscriptEntry.message.aiTitle/sessionId fields when applying the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@claude_code_log/renderer.py`:
- Around line 946-959: The prepare_session_ai_titles function currently
overwrites existing titles with blank/whitespace aiTitle values; change the loop
that handles AiTitleTranscriptEntry so it only assigns out[message.sessionId] =
message.aiTitle when message.aiTitle.strip() is non-empty (i.e., ignore
blank/whitespace titles) so legacy summaries aren't replaced by empty strings;
reference the prepare_session_ai_titles function and the
AiTitleTranscriptEntry.message.aiTitle/sessionId fields when applying the check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e6f8a97-e4fc-4951-a68b-f512c67f7f44

📥 Commits

Reviewing files that changed from the base of the PR and between 1265bb6 and 09c9f05.

📒 Files selected for processing (11)
  • claude_code_log/cache.py
  • claude_code_log/converter.py
  • claude_code_log/dag.py
  • claude_code_log/factories/transcript_factory.py
  • claude_code_log/migrations/006_session_ai_title.sql
  • claude_code_log/models.py
  • claude_code_log/renderer.py
  • claude_code_log/tui.py
  • test/test_ai_title.py
  • test/test_cache.py
  • test/test_html_regeneration.py
✅ Files skipped from review due to trivial changes (1)
  • test/test_ai_title.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • claude_code_log/factories/transcript_factory.py
  • claude_code_log/migrations/006_session_ai_title.sql
  • test/test_cache.py
  • test/test_html_regeneration.py
  • claude_code_log/dag.py
  • claude_code_log/cache.py
  • claude_code_log/tui.py
  • claude_code_log/models.py
  • claude_code_log/converter.py

@daaain daaain merged commit ca5afa3 into main May 9, 2026
11 checks passed
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.

2 participants