Commit 38321f5
Timeline UX + multi-file memory viewer + captain-hook v2.1.92 expansion (#55)
* adding modular copy options for all the md to rich text viewer + timeline
* scroll position restore on navigation and last opened session highlight
* mapping update task tabs on timeline with actual task texts
* fix: add skill_name route matcher to prevent file paths from matching [skill_name] segment
* fix: address code review findings on PR #55
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>
* fix: handle [Image #N] marker in JSONL merge (v2.1.83+ format)
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>
* feat(captain-hook): add 11 new hook types from Claude Code v2.1.83-v2.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.
* docs(features): Claude Code v2.1.81-v2.1.92 audit remediation plan
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)
* docs(features): multi-file project memory UI design
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>
* feat(api): multi-file project memory endpoints
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>
* feat(frontend): multi-file memory viewer with hover previews
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>
* fix(frontend): prevent stale fetch from clobbering memory panel state
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>
* refactor(frontend): dedupe markdown pipeline and type badge in memory 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>
* style(api): remove unused pytest import in test_conversation_endpoints
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>
* style(api): apply ruff formatter to conversation_endpoints and jsonl_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>
---------
Co-authored-by: Jayant Devkar <55962509+JayantDevkar@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 359cc58 commit 38321f5
72 files changed
Lines changed: 6961 additions & 162 deletions
File tree
- api
- models
- routers
- services
- tests
- api
- captain-hook
- docs/hooks
- src/captain_hook
- tests
- docs/features
- frontend
- src
- lib
- actions
- components
- agents
- commands
- memory
- __tests__
- plan
- skills
- timeline
- utils
- params
- routes
- agents/[name]
- commands/[command_name]
- plugins/[plugin_id]/skills/[...path]
- projects/[project_slug]
- sessions
- skills
- [...path]
- [skill_name=skill_name]
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
18 | 82 | | |
19 | 83 | | |
20 | 84 | | |
| |||
23 | 87 | | |
24 | 88 | | |
25 | 89 | | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
26 | 95 | | |
27 | 96 | | |
28 | 97 | | |
| |||
38 | 107 | | |
39 | 108 | | |
40 | 109 | | |
| 110 | + | |
| 111 | + | |
41 | 112 | | |
42 | 113 | | |
43 | 114 | | |
44 | 115 | | |
45 | 116 | | |
46 | 117 | | |
47 | 118 | | |
48 | | - | |
49 | | - | |
50 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
51 | 130 | | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
0 commit comments