Skip to content

Extract JSONL parser monolith into focused modules#57

Merged
wpak-ai merged 5 commits into
masterfrom
refactor/jsonl-parser-split
May 28, 2026
Merged

Extract JSONL parser monolith into focused modules#57
wpak-ai merged 5 commits into
masterfrom
refactor/jsonl-parser-split

Conversation

@clean6378-max-it
Copy link
Copy Markdown
Collaborator

@clean6378-max-it clean6378-max-it commented May 28, 2026

Closes #53

Summary

Mechanical split of the utils/jsonl_parser.py monolith (~761 lines) into focused modules with no behavior change. The 15-predicate tool-result dispatch table moves to utils/tool_dispatch.py with an explicit ordering contract. quick_session_info moves to utils/session_peek.py. Shared content helpers move to utils/jsonl_helpers.py. All existing imports from utils.jsonl_parser continue to work via re-exports.

Changes

  • utils/tool_dispatch.py_TOOL_RESULT_DISPATCH, all _tool_result_pred_* / _tool_result_build_*, _parse_tool_result
  • utils/session_peek.pyquick_session_info() two-pass metadata peek
  • utils/jsonl_helpers.py_entry_message, _normalize_content, _extract_text, _extract_images, _infer_title, _strip_system_tags
  • utils/jsonl_parser.py (slimmed) — parse_session(), message processors, _track_file_activity, re-exports
  • docs/architecture.md — dispatch table location updated to utils/tool_dispatch.py

Dispatch predicate order is unchanged (first match wins; task_message before narrower task arms).

Backward compatibility

Existing imports unchanged:

  • from utils.jsonl_parser import parse_session
  • from utils.jsonl_parser import quick_session_info
  • from utils.jsonl_parser import _parse_tool_result, _TOOL_RESULT_DISPATCH
  • from utils.jsonl_parser import _strip_system_tags

Test plan

  • pytest tests/test_jsonl_parser.py tests/test_real_session_fixtures.py tests/test_jsonl_validation.py -v
  • pytest -q
  • mypy -p api -p utils -p models
  • Import smoke: from utils.jsonl_parser import parse_session, quick_session_info, _parse_tool_result, _TOOL_RESULT_DISPATCH
  • Tuesday dispatch-order regression (test_real_session_fixtures.py) passes without modification

Out of scope

  • No dispatch reordering or new tool-result types
  • No runtime validation changes
  • No export engine or CLI changes

Summary by CodeRabbit

  • Improvements
    • Faster, low-overhead session metadata scan for quicker title and timestamp previews.
    • More robust parsing and normalization of tool outputs for clearer, more consistent display.
    • Better extraction and normalization of session content (text and images) for more accurate rendering.
  • New Features
    • Heuristic session title inference when no explicit title is present.
  • Documentation
    • Updated architecture docs clarifying parsing and dispatch behavior and ordering guidance.
  • Tests
    • Added test to prevent misclassification of tool result types.

Review Change Stack

@clean6378-max-it clean6378-max-it self-assigned this May 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

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

Split the monolithic JSONL parser into focused modules: utils/jsonl_helpers.py (content helpers), utils/tool_dispatch.py (ordered tool-result dispatch), utils/session_peek.py (quick metadata peek), updated utils/jsonl_parser.py to re-export, updated docs, and a dispatch-order test added.

Changes

JSONL Parser Modularization

Layer / File(s) Summary
Shared content and message helpers
utils/jsonl_helpers.py
Message field extraction, content normalization (string/list-of-dicts), text-only block concatenation, base64 image collection (including nested tool_result content), title inference from first user message, and system tag stripping via regex.
Tool-result dispatch table extraction
utils/tool_dispatch.py, tests/test_jsonl_parser.py
Predicate and builder functions for tool-result types (bash, file ops, plan, web, tasks, todos, user input, unknown) moved into an ordered _TOOL_RESULT_DISPATCH registry; _parse_tool_result() validates input and returns the first-match normalized payload; test ensures plan+content isn't misclassified as file_write.
Session metadata-only peek
utils/session_peek.py
quick_session_info() performs a two-pass JSONL scan (head-pass for title/first timestamp, optional tail-pass for last timestamp in large files) and returns title (fallback "Untitled Session"), first_timestamp, and last_timestamp.
Slimmed parser re-exports and wiring
utils/jsonl_parser.py
Re-exports helpers from utils.jsonl_helpers, dispatch from utils.tool_dispatch, and quick_session_info from utils.session_peek; removes local implementations and stops populating metadata["web_fetches"] from WebFetch/WebSearch tool inputs.
Architecture docs
docs/architecture.md
Diagram and Layers table updated to reference session_peek and tool_dispatch; dispatch table guidance moved to utils/tool_dispatch.py with ordering constraints and test pointers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • timon0305
  • wpak-ai

Poem

🐰 I hopped through tangled parser vines,
Split functions into tidy lines,
Dispatch now stands in its own glade,
Helpers hum, and peeks parade,
The rabbit cheers — the code aligns.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% 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
Title check ✅ Passed The title 'Extract JSONL parser monolith into focused modules' accurately and clearly summarizes the main mechanical refactor of splitting a monolithic file into three focused modules.
Linked Issues check ✅ Passed All primary coding requirements from issue #53 are addressed: tool-result dispatch isolated in utils/tool_dispatch.py with ordering documentation, quick_session_info moved to utils/session_peek.py, shared helpers in utils/jsonl_helpers.py, parse_session and re-exports preserved in slimmed utils/jsonl_parser.py, and docs/architecture.md updated.
Out of Scope Changes check ✅ Passed All changes are within scope: mechanical refactoring of jsonl_parser monolith into focused modules, no dispatch reordering, no behavior changes, and no export/CLI modifications beyond documented re-exports.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 refactor/jsonl-parser-split

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

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: 1

🧹 Nitpick comments (1)
docs/architecture.md (1)

74-79: ⚡ Quick win

Align contributor guidance with the actual dispatch-order contract.

Line 78’s “specific before generic” guidance conflicts with the documented broad-before-specific task_message exception and can trigger accidental reordering regressions. Please reword this step to emphasize preserving established table order unless fixtures/tests are updated.

🤖 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 `@docs/architecture.md` around lines 74 - 79, The docs guidance for adding tool
renderers is misleading about dispatch order: update the step in
docs/architecture.md to instruct contributors to preserve the existing predicate
order in utils/tool_dispatch.py (specifically the _parse_tool_result dispatch
table) unless they also update tests/fixtures and the ordering-protecting tests
(tests/test_jsonl_parser.py and tests/test_real_session_fixtures.py); explicitly
call out the special-case task_message behavior so readers know it is the
intentional broad-before-specific exception and that any change to ordering
requires adding/updating JSONL fixtures under tests/fixtures/ and the related
tests.
🤖 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 `@utils/session_peek.py`:
- Around line 24-27: The early 80-line cutoff can leave last_timestamp stale
when the file is small (≤10KB) but has >80 lines; modify the loop that
increments lines_read and breaks when lines_read > 80 so it only enforces the
cutoff for files larger than 10KB (use os.path.getsize or f.seek/tell to
determine size). Specifically, in utils/session_peek.py change the logic around
the lines_read break in the first-pass loop (and the analogous second-pass loop
referenced) so that the break executes only if file_size > 10 * 1024; this
ensures small files always get fully scanned and last_timestamp is accurate.

---

Nitpick comments:
In `@docs/architecture.md`:
- Around line 74-79: The docs guidance for adding tool renderers is misleading
about dispatch order: update the step in docs/architecture.md to instruct
contributors to preserve the existing predicate order in utils/tool_dispatch.py
(specifically the _parse_tool_result dispatch table) unless they also update
tests/fixtures and the ordering-protecting tests (tests/test_jsonl_parser.py and
tests/test_real_session_fixtures.py); explicitly call out the special-case
task_message behavior so readers know it is the intentional
broad-before-specific exception and that any change to ordering requires
adding/updating JSONL fixtures under tests/fixtures/ and the related tests.
🪄 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: dd572922-9f55-45f3-b311-020101280ae7

📥 Commits

Reviewing files that changed from the base of the PR and between 75b8174 and e3ab28b.

📒 Files selected for processing (5)
  • docs/architecture.md
  • utils/jsonl_helpers.py
  • utils/jsonl_parser.py
  • utils/session_peek.py
  • utils/tool_dispatch.py

Comment thread utils/session_peek.py
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@clean6378-max-it
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, please review this PR in more depth again

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== utils/jsonl_helpers.py ==="
cat utils/jsonl_helpers.py

echo ""
echo "=== utils/session_peek.py ==="
cat utils/session_peek.py

echo ""
echo "=== utils/tool_dispatch.py ==="
cat utils/tool_dispatch.py

echo ""
echo "=== utils/jsonl_parser.py ==="
cat utils/jsonl_parser.py

echo ""
echo "=== docs/architecture.md ==="
cat docs/architecture.md

echo ""
echo "=== tests/test_jsonl_parser.py (first 100 lines) ==="
head -100 tests/test_jsonl_parser.py 2>/dev/null || echo "Not found"

echo ""
echo "=== Imports / circular check ==="
rg "^from utils" utils/ --type py
rg "^import utils" utils/ --type py

Length of output: 42491



@clean6378-max-it — here is a detailed review of all changed files.


utils/tool_dispatch.py⚠️ Most Critical Issues

1. task_message predicate may shadow task_retrieval, task_completed, and task_async

The predicate for task_message is:

def _tool_result_pred_task_message(tr: dict[str, Any]) -> bool:
    return "task_id" in tr or "message" in tr

It sits before task_retrieval, task_completed, and task_async in _TOOL_RESULT_DISPATCH. Any real payload that has a "message" field (e.g., a task-completed result with a summary message, or a retrieval result with a status message) will be stolen here and classified as a generic task, losing the richer fields extracted by the later builders. The code comment acknowledges this ("Refining order needs golden fixtures; track as follow-up if real collisions appear"), but the task_retrieval / task_completed / task_async predicates are effectively dead code for any payload that also carries a "message" key. Consider opening a follow-up issue to add golden fixtures specifically for these three shapes so the ordering risk can be evaluated and, if necessary, the predicates moved above task_message.

2. plan predicate can be silently stolen by file_write

def _tool_result_pred_file_write(tr: dict[str, Any]) -> bool:
    return "filePath" in tr and "content" in tr

def _tool_result_pred_plan(tr: dict[str, Any]) -> bool:
    return "plan" in tr and "filePath" in tr

If any plan payload also carries a "content" key (e.g., the plan text), it will be classified as file_write before plan even gets evaluated. A stricter predicate would be:

def _tool_result_pred_plan(tr: dict[str, Any]) -> bool:
    return "plan" in tr and "filePath" in tr and "content" not in tr

or equivalently, move plan above file_write (with a note explaining why).

3. Empty-string content silently excluded in grep and file_read builders

# grep
content = tr.get("content", "")
if content and isinstance(content, str):
    result["content"] = content

An empty string result (valid — tool ran, found nothing) is excluded from the returned dict entirely. Callers that do result.get("content", "") will get the default, so it's not a bug, but it introduces an inconsistency: sometimes the key is absent, sometimes it's a non-empty string. Consider if isinstance(content, str): result["content"] = content to keep the field always present.

4. _tool_result_build_file_edit drops structuredPatch

The predicate fires on "structuredPatch" in tr, yet the builder never stores it:

def _tool_result_build_file_edit(tr: dict[str, Any], base: dict[str, Any]) -> dict[str, Any]:
    result = dict(base)
    result["result_type"] = "file_edit"
    result["file_path"] = tr.get("filePath", "")
    result["replace_all"] = tr.get("replaceAll", False)
    return result

If callers or UI code ever want to render a diff, they'd need to re-parse the original tool_result. If this is intentional (too large to store), add a comment explaining the decision.


utils/session_peek.py — Medium Issues

5. Title-derivation logic is duplicated and can diverge from infer_title

quick_session_info derives the title inline:

if title is None and entry.get("type") == "user":
    msg = entry_message(entry)
    text = extract_text(msg.get("content", []))
    if text:
        clean = strip_system_tags(text).strip()
        first_line = clean.split("\n")[0][:100]
        if first_line:
            title = first_line

infer_title in jsonl_helpers.py does the same but operates on post-parsed MessageDict objects. Any future change to one path (e.g., skipping tool-result-only user entries) must be mirrored in the other. Consider extracting the common strip → first-line → truncate step into a private helper in jsonl_helpers.py so both paths share it:

def _first_clean_line(text: str, max_chars: int = 100) -> str:
    return strip_system_tags(text).strip().split("\n")[0][:max_chars]

6. Pass-2 chunk can start mid-line — no issue, but undocumented

f.seek(file_size - chunk_size)
tail = f.read().decode("utf-8", errors="replace")
for line in reversed(tail.splitlines()):

The first "line" after seeking is almost certainly a partial UTF-8 line. It will fail json.loads and be silently skipped, which is correct, but a short comment here would help readers trust the logic quickly.


utils/jsonl_helpers.py — Low Issues

7. extract_images doesn't recurse into nested tool_result content that is itself a string

elif part.get("type") == "tool_result":
    nested = part.get("content", [])
    if isinstance(nested, list):
        ...

If content is a string (normalized elsewhere to [{type: text, text: ...}] but not here), the image extraction silently skips it. This is consistent with the current JSONL schema, but worth a comment.


utils/jsonl_parser.py — Low Issues

8. _entry_message is used internally but absent from __all__

__all__ = [
    "parse_session",
    "quick_session_info",
    "_parse_tool_result",
    ...
    # _entry_message is NOT here
]

If any external caller imports _entry_message from utils.jsonl_parser (it was previously defined there), the import still works at runtime (Python doesn't enforce __all__) but it's not part of the documented re-export contract. If it was previously importable, add it to __all__ for completeness.


docs/architecture.md — Low Issue

9. Component diagram doesn't reflect the new module split

The diagram still shows jsonl_parser as a monolith. The three new modules (tool_dispatch.py, session_peek.py, jsonl_helpers.py) are invisible in it. Updating the diagram would make the architecture self-documenting. A minimal update to the layers table could also mention the sub-modules:

Layer Responsibility Key modules
Parsing JSONL → session dict utils/jsonl_parser.py, utils/tool_dispatch.py, utils/session_peek.py, utils/jsonl_helpers.py

Summary table

# Severity File Issue
1 ⚠️ High tool_dispatch.py task_message predicate shadows task_retrieval / task_completed / task_async for payloads with a "message" key
2 ⚠️ Medium tool_dispatch.py plan predicate shadowed by file_write if plan payloads carry "content"
3 🔵 Low tool_dispatch.py grep / file_read builders exclude empty-string content fields
4 🔵 Low tool_dispatch.py file_edit builder silently drops structuredPatch without explanation
5 🔵 Medium session_peek.py Title-derivation logic duplicated vs infer_title; divergence risk
6 🔵 Low session_peek.py Pass-2 partial-line behaviour is correct but undocumented
7 🔵 Low jsonl_helpers.py extract_images doesn't handle string-typed tool_result content
8 🔵 Low jsonl_parser.py _entry_message absent from __all__
9 🔵 Low architecture.md Component diagram doesn't reflect new module split

The mechanical split itself is clean — no circular imports, backward-compatible re-exports, and the __all__ list is well-maintained. The dispatch ordering risk (item 1) is the one I'd track most carefully with concrete fixture tests before this merges.

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: 0

🧹 Nitpick comments (1)
utils/tool_dispatch.py (1)

245-247: ⚡ Quick win

Lock plan-before-file_write precedence with a regression fixture.

This ordering is critical and easy to accidentally break later. Please add a small overlap fixture asserting payloads containing plan + filePath + content classify as result_type == "plan".

🤖 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 `@utils/tool_dispatch.py` around lines 245 - 247, The plan-vs-file_write
precedence is fragile: add a regression test that constructs a tool payload
containing a plan plus filePath and content and asserts the dispatch
classification uses the plan branch (i.e., result_type == "plan"); specifically
create a small test that feeds such overlapping payloads into the dispatch logic
that uses _tool_result_pred_plan/_tool_result_build_plan and
_tool_result_pred_file_write/_tool_result_build_file_write and assert the
_tool_result_pred_plan predicate wins (and the built result equals what
_tool_result_build_plan would produce) to lock the ordering.
🤖 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 `@utils/tool_dispatch.py`:
- Around line 245-247: The plan-vs-file_write precedence is fragile: add a
regression test that constructs a tool payload containing a plan plus filePath
and content and asserts the dispatch classification uses the plan branch (i.e.,
result_type == "plan"); specifically create a small test that feeds such
overlapping payloads into the dispatch logic that uses
_tool_result_pred_plan/_tool_result_build_plan and
_tool_result_pred_file_write/_tool_result_build_file_write and assert the
_tool_result_pred_plan predicate wins (and the built result equals what
_tool_result_build_plan would produce) to lock the ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2aa7654b-8d42-4e87-9ff4-17d77ce85f14

📥 Commits

Reviewing files that changed from the base of the PR and between b0b712b and ddefa46.

📒 Files selected for processing (5)
  • docs/architecture.md
  • utils/jsonl_helpers.py
  • utils/jsonl_parser.py
  • utils/session_peek.py
  • utils/tool_dispatch.py
✅ Files skipped from review due to trivial changes (1)
  • docs/architecture.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • utils/jsonl_helpers.py

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

Comment thread utils/session_peek.py Outdated
Comment thread utils/session_peek.py Outdated
Comment thread utils/jsonl_helpers.py
Comment thread utils/jsonl_parser.py
Comment thread utils/jsonl_parser.py Outdated
Comment thread tests/test_jsonl_parser.py
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@clean6378-max-it
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, @timon0305 's comments are all addressed?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch all review comments from PR `#57`
gh pr view 57 --repo cppalliance/claude-code-chat-browser --json reviews,comments

echo "=== Review Comments ==="
gh api repos/cppalliance/claude-code-chat-browser/pulls/57/comments --paginate | jq '[.[] | select(.user.login == "timon0305")] | {count: length, comments: [.[] | {id, path, line, body: .body[0:300], resolved: .resolved}]}'

echo "=== Issue Comments ==="
gh api repos/cppalliance/claude-code-chat-browser/issues/57/comments --paginate | jq '[.[] | select(.user.login == "timon0305")] | {count: length, comments: [.[] | {id, body: .body[0:500]}]}'

echo "=== All Reviews ==="
gh api repos/cppalliance/claude-code-chat-browser/pulls/57/reviews --paginate | jq '[.[] | select(.user.login == "timon0305")] | {count: length, reviews: [.[] | {id, state, body: .body[0:300]}]}'

Length of output: 51660


🏁 Script executed:

#!/bin/bash
# Check current session_peek.py for the specific issues timon0305 flagged
echo "=== utils/session_peek.py ==="
cat utils/session_peek.py

echo ""
echo "=== Check PR description for test mention ==="
gh pr view 57 --repo cppalliance/claude-code-chat-browser --json body | jq -r '.body' | head -60

echo ""
echo "=== timon0305 review comments with resolved status ==="
gh api repos/cppalliance/claude-code-chat-browser/pulls/57/comments --paginate | jq '[.[] | select(.user.login == "timon0305")] | [.[] | {id, path, line, body: .body[0:400]}]'

Length of output: 8771



@clean6378-max-it — here's the status of @timon0305's six comments:

# File Comment Status
1 utils/session_peek.py Head scan was no longer capped at 80 lines for small files (≤10 KB) — behavioral change Addressed — current code applies the _MAX_HEAD_LINES = 80 break unconditionally for all file sizes
2 utils/session_peek.py Threshold was > 10240 instead of the original > 10000 Addressed_TAIL_READ_MIN_BYTES = 10000 now matches the original
3 utils/jsonl_helpers.py Positive comment on first_title_line extraction No action needed (praise)
4 utils/jsonl_parser.py Positive comment on backward-compat aliasing No action needed (praise)
5 utils/jsonl_parser.py Optional suggestion: simplify to a plain from utils.session_peek import quick_session_info as quick_session_info Optional — reviewer explicitly marked it as optional
6 tests/test_jsonl_parser.py PR description says "No test file changes" but a new dispatch-order regression test was added Not yet addressed — the PR description Summary still reads "No test file changes"

One outstanding item: the PR description needs a small update. The Summary still says "No test file changes" but a new test was in fact added (and @timon0305 called it "great"). You can update the description to remove or correct that claim — for example, change "No test file changes" to "Added dispatch-order regression test for plan-before-file_write classification".

Everything else from @timon0305's review is either fixed or positive — the review should be re-approvable once the description is updated.

@timon0305 timon0305 requested a review from wpak-ai May 28, 2026 22:20
@wpak-ai wpak-ai merged commit 4b2a4c6 into master May 28, 2026
6 checks passed
@wpak-ai wpak-ai deleted the refactor/jsonl-parser-split branch May 28, 2026 23:06
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.

claude-code-chat-browser: Extract JSONL parser monolith into focused modules

3 participants