From 411bf9f0fae34fb8bf72637a03c15b8c0a5db3ba Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Thu, 8 Oct 2026 00:37:38 +0200 Subject: [PATCH 1/2] Session previews: skip leading YAML front matter A session's preview is the first user message's label in the table of contents, the index cards and the TUI. For a message opening with front matter, the preview showed the YAML block. It now starts at the body, using the same detector the rendering uses. A `---` followed by a blank line is still a horizontal rule and stays. A message holding only front matter gives an empty preview, so the next user message supplies it, as for any empty first message. Previews already stored in the cache keep their front matter until the session is cached again. There is no cache invalidation for this: the case is rare. Co-Authored-By: Claude Opus 5.5 --- claude_code_log/utils.py | 7 ++ test/test_session_preview_frontmatter.py | 93 ++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 test/test_session_preview_frontmatter.py diff --git a/claude_code_log/utils.py b/claude_code_log/utils.py index 83c622e7..ce70cc10 100644 --- a/claude_code_log/utils.py +++ b/claude_code_log/utils.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Callable, Optional, TypeVar +from .frontmatter import split_frontmatter from .models import ( ContentItem, DEFAULT_DEPTH, @@ -740,6 +741,12 @@ def create_session_preview(text_content: str) -> str: flags=re.DOTALL, ).strip() + # Skip leading YAML front matter, as the rendering does: the preview + # starts at the body. Front matter alone leaves an empty preview. + split = split_frontmatter(preview_content) + if split is not None: + preview_content = split[1].strip() + # Strip command-tag XML soup down to ``/cmd`` or inner-text shape. preview_content = simplify_command_tags(preview_content) diff --git a/test/test_session_preview_frontmatter.py b/test/test_session_preview_frontmatter.py new file mode 100644 index 00000000..0cc12ed9 --- /dev/null +++ b/test/test_session_preview_frontmatter.py @@ -0,0 +1,93 @@ +"""A session preview skips the first message's leading YAML front matter. + +The preview (the first user message's label in the table of contents, +the index cards and the TUI) starts at the body, as the rendering shows +it. It uses the rendering's own detector, so a ``---`` followed by a +blank line is a horizontal rule and stays in the preview. A message +holding only front matter gives an empty preview, and the next user +message supplies it, as for any empty first message. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import pytest + +from claude_code_log.converter import compute_session_data, load_transcript +from claude_code_log.renderer import _collect_session_info +from claude_code_log.utils import create_session_preview + +FRONT_MATTER = "---\nname: release-checklist\ndescription: steps\n---\n\n" +BODY = "Please tag the release once CI is green." + + +class TestCreateSessionPreview: + def test_front_matter_is_skipped(self) -> None: + assert create_session_preview(FRONT_MATTER + BODY) == BODY + + def test_front_matter_only_is_empty(self) -> None: + assert create_session_preview("---\nonly: front matter\n---\n") == "" + + def test_a_leading_rule_is_unchanged(self) -> None: + text = "---\n\nA rule first, then text." + assert create_session_preview(text) == text + + def test_ordinary_previews_are_unchanged(self) -> None: + assert create_session_preview(BODY) == BODY + assert create_session_preview("A note\n---\nlate: block\n---") == ( + "A note\n---\nlate: block\n---" + ) + + def test_front_matter_after_a_system_reminder(self) -> None: + # The reminder is not part of the rendered text, so the front + # matter is still at its start. + text = "context\n" + FRONT_MATTER + BODY + assert create_session_preview(text) == BODY + + +def _user(uuid: str, parent: str | None, minute: int, text: str) -> dict[str, Any]: + return { + "type": "user", + "timestamp": f"2026-04-19T10:{minute:02d}:00.000Z", + "parentUuid": parent, + "uuid": uuid, + "isSidechain": False, + "userType": "external", + "cwd": "/tmp", + "sessionId": "s1", + "version": "2.1.34", + "message": {"role": "user", "content": text}, + } + + +@pytest.fixture +def messages(tmp_path: Path, request: pytest.FixtureRequest) -> Any: + first, second = request.param + entries = [_user("u1", None, 0, first), _user("u2", "u1", 1, second)] + path = tmp_path / "s1.jsonl" + path.write_text("\n".join(json.dumps(e) for e in entries) + "\n", encoding="utf-8") + return load_transcript(path) + + +CASES = [ + pytest.param((FRONT_MATTER + BODY, "A later question."), BODY, id="front-matter"), + pytest.param( + ("---\nonly: front matter\n---\n", "A later question."), + "A later question.", + id="front-matter-only", + ), + pytest.param((BODY, "A later question."), BODY, id="ordinary"), +] + + +@pytest.mark.parametrize(("messages", "expected"), CASES, indirect=["messages"]) +class TestSessionPreviews: + def test_cached_session_data(self, messages: Any, expected: str) -> None: + assert compute_session_data(messages)["s1"].first_user_message == expected + + def test_session_navigation(self, messages: Any, expected: str) -> None: + sessions, _, _ = _collect_session_info(messages, {}) + assert sessions["s1"]["first_user_message"] == expected From e37a276edb42aedbbee5be6f9750af64c4c00873 Mon Sep 17 00:00:00 2001 From: Christian Boos Date: Thu, 8 Oct 2026 00:43:04 +0200 Subject: [PATCH 2/2] Session previews: import the front-matter detector lazily utils is imported by nearly every module. A top-level import of the detector loaded PyYAML and wenmode's front-matter plugin on every CLI start: about +54 ms, or +23% on `import claude_code_log.cli`. It is now imported inside create_session_preview, and only for text that starts with `---`, as the other front-matter call sites do. Co-Authored-By: Claude Opus 5.5 --- claude_code_log/utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/claude_code_log/utils.py b/claude_code_log/utils.py index ce70cc10..2278b918 100644 --- a/claude_code_log/utils.py +++ b/claude_code_log/utils.py @@ -8,7 +8,6 @@ from pathlib import Path from typing import Callable, Optional, TypeVar -from .frontmatter import split_frontmatter from .models import ( ContentItem, DEFAULT_DEPTH, @@ -742,10 +741,15 @@ def create_session_preview(text_content: str) -> str: ).strip() # Skip leading YAML front matter, as the rendering does: the preview - # starts at the body. Front matter alone leaves an empty preview. - split = split_frontmatter(preview_content) - if split is not None: - preview_content = split[1].strip() + # starts at the body. Front matter alone leaves an empty preview. The + # import is lazy (it brings in PyYAML), and only text that could open + # with front matter pays for it. + if preview_content.startswith("---"): + from .frontmatter import split_frontmatter + + split = split_frontmatter(preview_content) + if split is not None: + preview_content = split[1].strip() # Strip command-tag XML soup down to ``/cmd`` or inner-text shape. preview_content = simplify_command_tags(preview_content)