feat(serve): local web UI + read-only API for capshelf projects - #1
Open
genged wants to merge 6 commits into
Open
feat(serve): local web UI + read-only API for capshelf projects#1genged wants to merge 6 commits into
genged wants to merge 6 commits into
Conversation
Install Anthropic's frontend-design skill and Vercel Labs' vercel-react-best-practices skill via skills.sh for the web UI work. Tracked in skills-lock.json; .claude/skills symlinks point at the real directories under .agents/skills. Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y
Add `capshelf serve`: a Bun.serve server exposing a read-only JSON API and a React (Vite) single-page UI for inspecting a project's reconcile state. - src/status-report.ts: extract the status computation from the status command into a shared buildStatusReport() so the CLI and the API share one source of truth (status --json output is unchanged). - src/serve-api.ts: /api/health, /api/status, /api/catalog over the existing core; GET-only (writes return 405). - src/commands/serve.ts: Bun.serve wiring, static SPA serving with traversal guard and a not-built hint page; --port/--host/--no-open. - web/: React + Vite app in the Patina theme (OKLCH; color reserved for drift status, mono for SHAs). App shell, Overview, Items master-detail with a live diff drawer, Shelf, and Bundles screens. Light + dark. Read-only v1: data-repo/network writes stay deliberate in the CLI. Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y
Binary embedding (1): - scripts/embed-web.ts generates src/web-embed.ts (base64 of web/dist) so `bun build --compile` bakes the UI into the single binary. - src/web-embed.ts: committed empty stub; serve falls back to web/dist on disk when empty (dev path), serves embedded assets when populated. - Makefile `web` target builds + embeds; `build` compiles then restores the stub so the working tree stays clean. New screens (3): - /api/config + /api/activity (git.ts recentCommits) over existing core. - Settings: data-repo binding, paths, install mode, tracked-item counts. - Activity: data-repo commit timeline (empty-state when unbound). - Command palette (⌘K): fuzzy nav + items + shelf; ?cmdk=1 deep-link. Verified: typecheck, lint, compiled binary serves the embedded UI, all screens render against live data. Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y
Keep the server read-only and the data-repo-write boundary intact, but make every write action discoverable: clicking a write affordance opens a shared command dialog showing the exact `capshelf …` to copy and run. - CommandDialog: provider + useCommand() hook + dialog (copy button, note). ?cmd=<args> deep-links it open for shareable "run this" links. - Wired: topbar Apply, Overview row menu (update/revert/keep-local/status by state), Shelf Add (add <kind>/<name>), Bundles Add (add bundles/<name>), Item drawer actions (update/revert/keep-local). Drawer's inline command is replaced by contextual action buttons that open the dialog. Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y
Correctness: - serve.ts: path-traversal boundary now requires a trailing separator so a sibling dir like "<web/dist>-evil" can't satisfy the startsWith() check. - serve-api.ts /api/activity: parse `n` as a positive integer (was `Number(x) || 25`, which swallowed 0 and accepted floats fed to `git -n`). - serve-api.ts catalog: drop unsafe `as ItemKind` cast that defeated the runtime guard; use ITEM_KINDS.find for a real ItemKind | undefined. - web useFetch: shared fetch hook with an unmount guard; Activity/Settings no longer setState after unmount (also dedups their load/error boilerplate). Cleanup: - Share metadataJsonFields from metadata.ts (ls.ts + serve-api.ts) instead of two copies; merge the duplicate ./git import. - api.ts STATE_LABEL: drop the duplicate kept_local key and the redundant cast. - ItemScreen: remove the pass-through VersionInline wrapper; include row.state in the diff effect deps. Verified: typecheck, biome lint, web build, 478/479 tests (the one failure is a pre-existing environmental init test), serve smoke incl. traversal attempt. Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y
The serve feature's API and helpers had no direct tests (and were where the review found bugs). Add meaningful coverage: - serve-api.test.ts: drives handleApiRequest in-process against a real bound project + data repo — routing (null/405/404/500), and the shape/behavior of health, status, catalog (incl. kind filter + invalid-kind ignore), config, and activity (newest-first, head match, n cap + junk fallback). - serve-static.test.ts: staticTarget path confinement — the sibling-prefix bypass and parent traversal that the review fix closed. - recent-commits.test.ts: ordering, field parsing, limit, separator-unsafe subjects, and [] on a non-repo. Refactor: extract staticTarget() from serve.ts's serveStatic so the confinement check is unit-testable as a pure function. 500 tests, 1 pre-existing environmental failure (init bootstrap) unchanged. Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Install Anthropic's frontend-design skill and Vercel Labs'
vercel-react-best-practices skill via skills.sh for the web UI work.
Tracked in skills-lock.json; .claude/skills symlinks point at the
real directories under .agents/skills.
Claude-Session: https://claude.ai/code/session_01GqDEcrivurtwK3oW8STB9Y