7a81b7333e
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 29s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 53s
CI & Build / Build & push image (push) Successful in 1m2s
Step 3 of the snippet-merge milestone (#231): the human surfaces for merge + multi-location, at v1 quality. Frontend: - SnippetListView: a Select mode (checkbox on each card) → a sticky action bar → a merge modal that lets you pick which selected snippet is the canonical (the others fold in and go to trash). Accent border on selected cards, Moss action buttons (Hybrid rule). - SnippetEditorView: the single Location fieldset becomes a repeatable locations list (add/remove rows), so editing a merged snippet no longer collapses its call sites — no data loss. Sends `locations`. - SnippetDetailView: renders every location (Location vs Locations label). - api/snippets.ts: SnippetLocation type, `locations` on fields/input, mergeSnippets(). Backend (editor enablement): - create_snippet service + POST route accept an optional `locations` list; PATCH route forwards `locations` — so the editor's location list works uniformly on create and edit. Single repo/path/symbol remain the one-location shorthand (MCP create contract unchanged). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pa2EsuB54BuWQ8GfJq9c7t