Repository navigation
Extend extra session roots to all views, add machine badge (2/2, depends on #115) - #116
crazy-daddy wants to merge 6 commits into
Conversation
JayantDevkar
left a comment
There was a problem hiding this comment.
Reviewed the second commit only, as asked. The call-site changes are clean: the no-roots path matches main at every site I checked, no O(1) lookup became a scan, the dedupe and the partial-file skip are there, and the skip only wraps the remote loop so a corrupt local JSONL still surfaces as before. The badges copy the Desktop badge's classes and tokens and render nothing when machine is null. No blockers; this merges after #115 once the following are in.
Should fix
- No tests for this commit:
test_session_roots.pyhas the same 30 tests as #115. Add, with the real temp-dir fixture: browser dedupe (a session synced to two dirs counts once), a partial or unreadable remote file skipped while the rest still list,machineset on browser and detail responses for a synced session and null for a local one, andsession_jsonl_path/project_dirs_forreturning the local path with no roots. routers/projects.py:561-564: the "SQLite returned 0 sessions, fall back to disk" check globs onlyproject.project_dir, so a remote-only project not yet indexed gets no fallback. Useproject.list_session_paths().services/session_roots.py:569-574: with roots configured,machine_for_path/root_for_pathcallresolve()on the file and on every root for every session row. Cache the resolved base onSessionRoot.services/session_lookup.py:70-84: a detail request for a synced session scans every local project dir before trying the remote index. Checksessions.source_rootfirst, or try the remote index first when roots exist. Worth doing, not required.
Nits
routers/sessions.py:1880-1882: an exact remote hit returns immediately and drops local prefix matches already collected, which hides ambiguity for short prefixes.services/session_relationships.py:57-59: when the starting dir is a synced one, the remote copy wins duplicates; everywhere else local wins. Put the local dir first.routers/skills.py:621and:715,routers/tools.py:134,routers/agents.py:476build summaries withoutmachine, so synced sessions show no badge there. Say so in the PR or wire them up.routers/agent_analytics.py:60-61still hardcodes~/.claude/agents;:92-97(appending remote canonicals) is nearly dead code.SessionCard.svelte:226-243stacks the Desktop and machine badges as two rows in the compact layout;GlobalSessionCard.svelte:219-235puts them on one row. Match them.- One-line comments:
api/scripts/backfill_titles.py:90-91, and the two-line docstring summary atapi/db/queries_shells_cron.py:212-213.
Part 1 of 2 (stack). Part 2, feature/extra-roots-coverage, builds on this branch and extends coverage to the remaining views plus a machine badge. The sidecar hook (feature/extra-roots-hook) is independent. Users sync other machines' ~/.claude dirs here with any tool (Syncthing, OneDrive, rsync, ...) and list them in ~/.claude_karma/extra-roots.json or CLAUDE_KARMA_EXTRA_ROOTS. Karma reads them read-only; no sync daemon. Each remote project dir joins a local project by the first matching rule: project_map, karma-project.json sidecar (git identity + subdir), path_map prefix rewrite, same encoded name, unique folder name (guess). Unmatched dirs show as remote-only projects. Remote worktree dirs follow their project. - services/session_roots.py: root config, sidecars, matching, TTL cache - indexer: third pass for remote dirs, sessions.source_root column (no reindex), local copy wins, first root wins, offline roots keep their rows - Project.session_dirs: listing, lookup and agents span all dirs - session_lookup falls back to remote dirs - local git projects get a sidecar on every index pass - GET /extra-roots shows how each remote dir was matched - SETUP.md: Multiple Machines section
Blockers: - Write karma-project.json sidecars only while at least one extra root is configured. Without roots Karma no longer touches ~/.claude/projects. - match_by_name now defaults to off; the folder-name guess is opt-in per root. Should fix: - A malformed or mid-edit extra-roots.json raises in _load_root_entries; _refresh keeps the previous roots and flags config_ok=False, and the indexer skips the removed-root cleanup until the config loads cleanly. The indexer calls invalidate() (keeps the fallback) instead of clear_cache(). - Remote JSONL files are re-indexed when their size changed even if the mtime did not (cp -p, SMB stamp the final mtime before the data lands). - A sidecar identity claimed by two local clones is ambiguous and no longer matches rule 2. - GET /extra-roots is localhost-only via _require_local; refresh moved to POST /extra-roots/refresh. _require_local takes a subject for the 403 text. - Root validation rejects duplicate resolved paths, roots overlapping the local ~/.claude, and paths that are files. Missing paths stay allowed. - path_map: "/" (empty prefix) is ignored; prefixes are case-insensitive only for Windows paths (drive letter or UNC). Nits: - One-line comments in config.py, session_roots.py, schema.py. - SessionRoot caches its resolved base; root_for_path no longer resolves every root per session file. - Tests: malformed roots file, non-list file, nested/duplicate/file roots, path_map edge cases, ambiguous identity, partial remote file, endpoints, no roots means no sidecar write.
- path_map: a bare drive source ("C:/", normalised to "C:") is treated as a
Windows prefix and matched case-insensitively.
- SessionRoot resolves its base in __post_init__, so a root built without
resolved_base still matches symlinked paths in root_for_path.
- Tests: no roots also means no stale-sidecar delete; a malformed roots file
on a cold start (no previous roots in memory) keeps remote rows; drive-root
path_map case.
Part 2 of 2 (stack). Depends on feature/extra-roots-core (part 1): branched from it and needs its services/session_roots.py and the sessions.source_root column. Merge part 1 first; until then this branch's diff against main also shows part 1. - remaining call sites read synced dirs via session_jsonl_path / project_dirs_for / Project.session_dirs: sessions and projects routers (slug cache, prefix lookup, continuation lookup, JSONL fallback), session chain resolver, title cache, memory, cron fires, agent analytics, history, session filter, backfill_titles script - synced copies of one session count once; a partial file mid-sync is skipped without hiding the rest of its root - agent_analytics and backfill_titles use settings.projects_dir instead of a hardcoded ~/.claude/projects - SessionSummary.machine (from sessions.source_root) and a machine badge on session cards and the conversation header
Should fix: - Tests with the temp-dir fixture: browser dedupe of a session synced to two dirs, an unreadable remote file skipped while the rest list, machine set on browser and detail responses for a synced session and null for a local one, session_jsonl_path / project_dirs_for local with no roots, index-backed lookup, short-id prefix hits across dirs, local-first relationship resolver. - projects.py: the "SQLite returned 0 sessions" disk fallback uses project.list_session_paths(), so a remote-only project gets it too. - session_lookup.py: with roots configured, find_session_with_project asks the index for project and source_root first (one stat) and only scans when the index does not know the session or its file moved. A local copy wins even when the index still says synced. Nits: - skills, tools and agents summaries carry machine: the six query_sessions_by_* SELECTs include s.source_root, the JSONL fallback in skills uses machine_for_path. - session_relationships: local dir first when the start dir is a synced one. - agent_analytics: agents dir from settings.claude_base; the remote canonicals loop (dead: those projects' .claude/agents live on the other machine) is gone. - SessionCard compact layout puts Desktop and machine badges on one row, matching GlobalSessionCard. - One-line comments in backfill_titles.py and queries_shells_cron.py. - _find_session_files keeps returning an exact remote hit alone, like the local loop: an exact filename only exists for a full UUID, where prefix hits are moot, and the short-id resolver would otherwise pick by mtime.
|
Rebased onto the current #115 tip ( Should fix
Nits
Verified: |
fd15a2c to
e995732
Compare
The models-only CI job installs no web deps; the test imports routers.sessions, so it skips there like the endpoint tests do.
Important
Depends on #115 — merge that first. This branch is stacked on #115 and needs its
services/session_roots.pyand thesessions.source_rootcolumn. Until #115 is merged, the diff here also shows its commit: review only the second commit, "extend extra session roots to all views, add machine badge" (or comparefeature/extra-roots-core...feature/extra-roots-coverageon the fork).Summary
Part 2 of 2 of extra session roots (see #115 for the feature and configuration). #115 indexes synced sessions and makes the project list, project pages and session lookup see them. This PR covers every remaining place that read only the local
~/.claude/projectsdir, and labels synced sessions with the machine they came from.Changes
session_jsonl_path,project_dirs_for,Project.session_dirs):backfill_titlesscriptagent_analyticsandbackfill_titlesusesettings.projects_dirinstead of a hardcoded~/.claude/projectsSessionSummary.machine(fromsessions.source_root,nullfor this machine) and a machine badge (laptop icon + root name) on session cards, global session cards and the conversation header, next to the existing Desktop badgeWith no roots configured, every path behaves as before.
Testing
mainnpm run check: 0 errors (13 existing warnings, none in changed files)ruff check: no new findings