Timeline UX + multi-file memory viewer + captain-hook v2.1.92 expansion#55
Timeline UX + multi-file memory viewer + captain-hook v2.1.92 expansion#55JayantDevkar merged 15 commits intomainfrom
Conversation
…g [skill_name] segment
|
Five HIGH-severity issues plus supporting cleanups and tests. HIGH - ToolCallDetail: remove 'content' from skipKeys — the Write tool content preview was filtered out before the render path could read it, so feature #2 of PR #55 silently did not work. Kept 'task_subject' in skipKeys since it's read directly from event.metadata. - SessionCard + projects/[project_slug]: extract getSessionUrlIdentifier helper so the last-opened-highlight comparison uses the same slug/uuid resolution as SessionCard's href, fixing highlight loss on sessions where liveSession.slug differs from session.slug. - markdownCopyButtons: query h1/h2/h3 (was h2/h3); h1 and h2 always get a button, h3 keeps the 150-char gate. Matches the PR description. Removed stale h2-vs-h3 visual-distinction comment. - ToolCallDetail TaskUpdate: restore input.subject to hasChanges and add a "Rename to" row inside the Changes panel so a subject-only rename is visible as an explicit delta (header still shows inherited subject). - conversation_endpoints: tighten regex from r"Task #(\w+)" to r"Task #(\d+)" and move `import re` to module top. Added 3 regression tests in a new test_conversation_endpoints.py covering happy path, no matching TaskCreate, and unparseable result content. MEDIUM - markdownCopyButtons: delete dead `cleanupFns` array — it was populated but never invoked; DOM removal handles listener cleanup via GC. - jsonl_utils: replace copy.deepcopy(base) with {**base} shallow copy. The merge function only mutates the `message` key which is already rebuilt as a fresh dict, so deepcopy was wasted allocation for base64-image payloads. Drop `import copy`. - test_jsonl_utils: add TestMessageMerging with 8 tests — 4 direct unit tests of _merge_user_message_dicts (image-source drop, real-extra preservation, empty extra, legacy content key) and 4 integration tests via iter_messages_from_jsonl (same-timestamp merge, different-timestamp no-merge, cross-type no-merge, 3-way merge). - sessions/+page.svelte: make restoreScroll async and await tick() before requestAnimationFrame so scroll restore lands on fully-rendered DOM. LOW - ToolCallDetail: strip trailing newline before counting lines in the Write content preview so "a\nb\n" reads as 2 lines not 3. Verification - api: pytest tests/test_jsonl_utils.py tests/test_conversation_endpoints.py tests/test_session.py tests/test_agent.py — 149 passed. - frontend: svelte-check clean on all touched files (pre-existing errors only in skills/[skill_name=skill_name]/* from the PR's own route rename). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@the-non-expert — pushed code-review fixes in 🔴 HIGH (5 fixes)
🟡 MEDIUM (4 fixes)
🟢 LOW
Verification
Manual test plan — please verify before merging
Full diff: https://github.com/JayantDevkar/claude-code-karma/pull/55/files |
Post-audit regression fix for the merge logic added in the previous
commit. Claude Code v2.1.83 changed the image-attachment marker from
`[Image: source: /var/folders/...]` to `[Image #N]`, with a trailing
space added in v2.1.85+. The existing `startswith("[Image: source:")`
check missed the new format, so `_merge_user_message_dicts` would leave
the redundant marker text in the merged content for any session created
by a Claude Code version released after 2026-03-25.
Changes
- Extract a `_is_image_marker_text` helper that matches both prefixes.
- Update the docstring to call out the v2.1.83 + v2.1.85 format variants
so the next schema change is easier to spot.
- Add `test_merge_drops_image_hash_number_marker` covering a 2-image
attachment with both `[Image #1]` and `[Image #2] ` (trailing space)
marker variants in the extra message.
Verification: pytest tests/test_jsonl_utils.py → 18 passed.
Cite: Claude Code changelog v2.1.83 (2026-03-25) and v2.1.85 (2026-03-26).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
….1.92
Expand the captain-hook Pydantic library with 11 new hook event types
introduced between Claude Code v2.1.83 and v2.1.92, bringing the total
from 13 to 24 supported hooks.
New hook classes:
Context:
- InstructionsLoadedHook (CLAUDE.md / rules file loaded)
User interaction:
- PermissionDeniedHook (auto mode denied a tool call; can request retry)
- ElicitationHook (MCP server requests structured input)
- ElicitationResultHook (user response to MCP elicitation)
Filesystem (new module fs_hooks.py):
- CwdChangedHook (working directory changed)
- FileChangedHook (external file change detected)
Agent Teams (new module team_hooks.py, experimental):
- TaskCreatedHook
- TaskCompletedHook
- TeammateIdleHook
Worktree (new module worktree_hooks.py):
- WorktreeCreateHook (HTTP hook can override worktreePath)
- WorktreeRemoveHook
Output model changes:
- PreToolUseOutput.permission_decision now accepts "allow", "deny",
"ask", and "defer" (the new "defer" value supports the headless
`-p --resume` pause/resume flow).
- New PermissionDeniedOutput with a single retry: bool field for
requesting that Claude retry a denied tool call.
Other changes:
- HookEventName Literal in base.py extended with all 11 new event names.
- HOOK_TYPE_MAP, HookEvent union, and __all__ in src/captain_hook/__init__.py
extended with the new classes.
- Backward-compat shim models.py mirrors the new exports.
- 107 new tests added (fixtures, parser dispatch, round-trips, output
models): 131 -> 238 total tests, all passing.
- README.md and CLAUDE.md updated to reflect the 24-hook count and
the new module layout.
- 11 new docs/hooks/*-info-available.md files following the existing
template.
No existing hook classes or test cases were modified.
14 action items across 3 sprints tracking dashboard updates needed for Claude Code releases between 2026-03-20 and 2026-04-06. - Tier 1 (Blockers): JSONL merge regression (shipped), captain-hook expansion (11 new types), Teams discovery, bare mode detection - Tier 2 (High Priority): PowerShell/Teams tool recognition, frontmatter parsing (initialPrompt, shell, paths), managed-settings fragment merging, MCP metadata preservation, 14 new settings fields - Tier 3 (Experimental): Extended thinking visibility, hook conditional if support, session resumption hardening Cite: claude-code-guide audit (2026-04-06)
|
@the-non-expert — two more commits pushed on top of the earlier review fixes. The scope of this PR grew beyond timeline UX because I ran a Claude Code release audit (v2.1.81→v2.1.92) in the same session and chose to keep all follow-up work together per @JayantDevkar's preference rather than splitting into 3 PRs. Current commit stack on
|
| Suite | Count | Result |
|---|---|---|
pytest api/ |
1444 passed, 6 skipped | ✅ |
pytest captain-hook/ |
238 passed (baseline was 131) | ✅ |
| Svelte-check on touched files | clean | ✅ |
What I'd like you to focus on in review
- Timeline UX changes (your original work) — already reviewed by me in the earlier comment
- H13/H4/H10/H6/H1 review-fix commit (
13cd5bf) — needs manual test on the Write content panel and the last-opened session highlight [Image #N]regression (272f506) — purely additive pattern match, backed by a new test- captain-hook expansion (
cf7eb26) — please sanity-check the new hook models against the official Claude Code hook docs. The schemas are my best-effort interpretation of the audit report, and any field that was ambiguous I defaulted toOptional[str]to be permissive. The 3 sub-sections marked{needs clarification}in the audit plan correspond to exactly these ambiguities. - audit plan doc (
e122b3c) — content review only, it's a planning doc not code
Rollback strategy if anything is wrong
Each commit is independently revertable. The 4 new commits on top of your original 4 are ordered from lowest-risk (review fixes) to highest-risk-of-subjective-disagreement (audit plan). Revert from the top down if needed.
Scratch branches cleaned up
The work landed here via cherry-pick from two scratch branches (feat/captain-hook-v2.1.92-events and worktree-agent-a380b18f). Both are being deleted locally now that they're merged here. No remote scratch branches were pushed.
Design for reworking MemoryViewer to handle the new auto-memory layout (MEMORY.md index + topical child files with YAML frontmatter). Reader- first UX: hover previews (Wikipedia-style) for link metadata, side panel drawer for full-content reading, collapsible orphan section for files not referenced by the index. Covers API changes (2 endpoints), component split, backwards compatibility, and test strategy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the single-file /projects/{name}/memory response with a
{index, files} shape that enumerates every *.md in the project's
memory/ directory. Add /projects/{name}/memory/files/{filename}
for fetching individual child file content on demand.
- New schemas: MemoryIndexEntry, MemoryFileMeta, ProjectMemoryFileResponse
- Hand-rolled YAML frontmatter parser (no new dependency) with graceful
fallback on malformed blocks
- Markdown link extraction computes linked_from_index for each child
- Strict path validation on the per-file endpoint: regex allowlist,
reject path separators / .. / leading dot / null byte, plus a
resolve() + is_relative_to() containment check for symlink escapes
- Backwards compatible: projects with only MEMORY.md return files=[]
- 30 new tests covering all spec error cases and 12 path-traversal variants
Design: docs/features/2026-04-07-multi-file-memory-ui.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rework the project memory UI to handle the new auto-memory layout
(MEMORY.md index plus topical child files with YAML frontmatter).
Reader-first UX: the index narrative is the page. Links within the
index are rewritten to in-app interactive elements — hover shows a
Wikipedia-style popover with the target file's metadata (type, word
count, modified time), click opens the file in a right-side drawer.
Orphan files (present on disk but not referenced from MEMORY.md)
surface in a collapsible section at the bottom.
- MemoryViewer.svelte: refactored into a shell that owns state,
fetches the new {index, files} response, and debounces hover 150ms
- MemoryIndex.svelte: renders MEMORY.md through the existing marked +
DOMPurify pipeline, applies the rewriteMemoryLinks action
- MemoryHoverCard.svelte: floating popover with manual viewport flip
- MemoryFilePanel.svelte: bits-ui Dialog styled as a right-side sheet,
fetches /memory/files/{filename} on open, handles 400/403/404/network
errors with a Retry button, swaps content when filename changes
- MemoryOrphanList.svelte: collapsible "Other memory files" section
- rewriteMemoryLinks.ts: Svelte 5 action that post-processes the
rendered DOM, matches a[href$=".md"] anchors against known files,
attaches hover/click listeners, and marks broken links visually
- api-types.ts: replaced ProjectMemory with the new {index, files}
shape, added MemoryFileMeta, ProjectMemoryFile, MemoryFileType
- vite.config.ts: added svelteTesting() plugin (VITEST-gated, no-op
in production) to enable Svelte 5 component tests
- 37 new tests across 4 files covering loading/error/orphan states,
DOM rewriting lifecycle, hover card rendering, and panel refetch
Integration point at +page.svelte:1725 unchanged.
Design: docs/features/2026-04-07-multi-file-memory-ui.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MemoryFilePanel.fetchFile() mutated shared state (fileData, bodyFading, loading, error) without tracking which invocation was current. This created three reachable bugs with a single root cause — no fetch lifecycle management: 1. Out-of-order resolution: click A → click B → A's fetch resolves after B's → fileData ends up as A (wrong file shown permanently). 2. bodyFading race: A's 80ms setTimeout could fire while B was still loading, briefly showing A without the fade-out covering it. 3. Close-during-fetch leak: closing the panel mid-fetch cleared state, but the in-flight fetch still wrote fileData on resolve, leaking stale content into the next open. Fix: add a generation counter (fetchGen). Each fetchFile captures ++fetchGen at entry and checks `myGen !== fetchGen` after every await and in the finally block. Superseded invocations drop their state writes silently. Panel close also bumps fetchGen to invalidate any in-flight fetch. Chose a gen counter over AbortController for simplicity — the bandwidth saved by actual cancellation is negligible for a localhost API, and the code stays in one mental model (no try/catch on AbortError, no extra imports). Adds a regression test that resolves an older fetch AFTER a newer one and asserts the newer file's content remains visible. Test fails against the pre-fix code at the exact expected assertion. Verification: 261 frontend tests pass (up from 260), svelte-check clean, production build succeeds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… components Addresses two HIGH quality findings from the code review of the multi-file memory UI feature. No behavioral change — verified by the existing 261-test suite including the race regression test. 1. Reuse existing renderMarkdownEffect helper MemoryIndex.svelte and MemoryFilePanel.svelte both reimplemented the marked + DOMPurify pipeline inline (the same ~12-line block each), when utils.ts already exports renderMarkdownEffect — a helper used by 4 other routes (agents, about, commands, skills). Two copies of a security-critical sanitization pipeline drift over time; centralize on the existing helper. 2. Extract shared type badge module TYPE_BADGE_CLASSES, TYPE_LABELS, badgeClass, and badgeLabel were duplicated byte-for-byte across MemoryHoverCard, MemoryFilePanel, and MemoryOrphanList (~25 lines each × 3 = ~75 lines). Extract to a single memoryTypeBadge.ts module. MemoryOrphanList's badge needed `shrink-0` because it lives inside a flex row — accept optional extra classes via a second parameter to badgeClass() rather than forcing per-call string concatenation at every site. Net: -84 lines (+18 / -102) across 4 edited files + 1 new module. Verification: 261 tests pass, svelte-check clean (0 errors), build succeeds. The fetch-race regression test from the previous commit continues to pass, confirming the refactor did not reintroduce any of the bugs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes ruff F401 CI failure on PR #55. `pytest` was imported but never referenced — the tests use plain assert statements without fixtures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…utils tests Fixes Python Lint CI failure on PR #55. `ruff format --check` flagged two test files with whitespace drift; applying the formatter is idempotent and matches the repo's existing ruff 0.15.9 config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JayantDevkar
left a comment
There was a problem hiding this comment.
Self-approving as repo owner. Local verification: 1474 api + 238 captain-hook + 261 vitest + svelte-check + ruff all green. CI green on dbf5d8f after the formatter fix.
JayantDevkar
left a comment
There was a problem hiding this comment.
Self-approving as repo owner.
Summary
This PR started as timeline UX polish and grew into four themed chunks, kept bundled per the 2026-04-07 scope decision. All 13 commits pass CI locally (ruff, pytest, svelte-check, eslint, vitest).
What's in the PR
1. Timeline UX (original scope) — 4 commits
markdownCopyButtons.ts).TaskCreatetool results into ataskId → subjectmap; TaskUpdate events display the task description instead of justTask #N./sessionsand/projects/[slug](sessionStorage + beforeNavigate +await tick()+ rAF).[skill_name]segment.2. Code-review fix commit (
13cd5bf)Addresses 5 HIGH + 4 MEDIUM + 1 LOW findings from PR review — summary inline in the commit and the earlier PR comment. Key fixes: Write-tool content panel un-skipped, last-opened highlight drift between live/canonical slugs, H1 selector + code-block gating, TaskUpdate rename delta,
Task #(\d+)regex tightening,deepcopydropped from jsonl_utils merge.3. JSONL
[Image #N]marker fix (272f506)Claude Code v2.1.83+ serializes attached images as
[Image #N]placeholders. JSONL merge now recognizes and preserves the pattern. +1 regression test.4. Captain-hook v2.1.92 expansion (
cf7eb26)Eleven new hook models covering Claude Code v2.1.83–v2.1.92:
InstructionsLoaded,CwdChanged,FileChangedPermissionDenied,Elicitation,ElicitationResultTaskCreated,TaskCompleted,TeammateIdleWorktreeCreate,WorktreeRemovePlus
"defer"added toPreToolUseOutput.permissionDecision(v2.1.89) and a newPermissionDeniedOutputclass withretry: bool.HOOK_TYPE_MAPgrows from 13 → 24. Adds 3 domain modules (fs_hooks.py,team_hooks.py,worktree_hooks.py) and 11 reference docs. +107 tests (238 total, up from 131).5. Audit remediation plan (
e122b3c)docs/features/2026-04-06-claude-code-audit-action-items.md— 874-line planning doc tracking 14 action items across Blockers / High Priority / Experimental tiers for the v2.1.81→v2.1.92 remediation sprint. Docs-only change.6. Multi-file project memory (5 commits)
1b4634f): new endpoints onrouters/projects.pyfor list/read/write/delete of.claude/CLAUDE.mdsiblings. +448-line test suite intest_memory.py.4f13259):MemoryViewersplits intoMemoryIndex,MemoryFilePanel,MemoryOrphanList,MemoryHoverCard, plus arewriteMemoryLinks.tsaction that intercepts cross-file links and routes them through the viewer without a page load. +8 vitest suites.d770c15): sequence-gate onMemoryFilePanelfetch resolution to prevent a late response from clobbering a newer selection. +1 test.d5a9652):memoryTypeBadge.tsextracted soMemoryIndex,MemoryFilePanel,MemoryHoverCard,MemoryOrphanListshare a single source for type badges and the markdown pipeline.a3e9b54):docs/features/2026-04-07-multi-file-memory-ui.mdarchitecture spec.7. Ruff lint fix (
d51e389)Removes unused
pytestimport fromtest_conversation_endpoints.pyso Python Lint CI goes green.Verification
api/ pytestcaptain-hook/ pytestapi/ ruff checkfrontend/ svelte-checkfrontend/ eslintfrontend/ vitestManual test plan
/projects/[slug], press back, confirm the card you came from glows with the ring#input.subjectis present/sessionslands on the right page and scroll positionRollback strategy
Each commit is independently revertable. Ordered from lowest-risk (lint fix,
[Image #N]pattern match) to highest-risk-of-subjective-disagreement (audit plan, multi-file memory UX). Revert from top down if needed.🤖 Generated with Claude Code