feat(scribe): snippet merge UI — multi-select merge, repeatable locations
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
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
This commit is contained in:
@@ -73,6 +73,7 @@ async def create_snippet_route():
|
||||
repo=data.get("repo", ""),
|
||||
path=data.get("path", ""),
|
||||
symbol=data.get("symbol", ""),
|
||||
locations=data.get("locations"),
|
||||
tags=data.get("tags"),
|
||||
project_id=project_id,
|
||||
)
|
||||
@@ -109,6 +110,8 @@ async def update_snippet_route(snippet_id: int):
|
||||
# treats None as "leave unchanged"). Empty strings ARE applied — the form
|
||||
# sends the full field set, so a cleared field is an intentional clear.
|
||||
kwargs = {k: data[k] for k in _STR_FIELDS if k in data}
|
||||
if "locations" in data:
|
||||
kwargs["locations"] = data["locations"]
|
||||
if "tags" in data:
|
||||
kwargs["tags"] = data["tags"]
|
||||
if "project_id" in data:
|
||||
|
||||
@@ -264,17 +264,20 @@ async def create_snippet(
|
||||
repo: str = "",
|
||||
path: str = "",
|
||||
symbol: str = "",
|
||||
locations: list[dict] | None = None,
|
||||
tags: list[str] | None = None,
|
||||
project_id: int | None = None,
|
||||
):
|
||||
"""Create a snippet note (embedded on create for immediate recall). Returns
|
||||
the created Note."""
|
||||
the created Note. Pass ``locations`` for the multi-location case; the single
|
||||
``repo``/``path``/``symbol`` are the one-location shorthand."""
|
||||
note = await notes_svc.create_note(
|
||||
user_id,
|
||||
title=compose_title(name, when_to_use),
|
||||
body=compose_body(
|
||||
code=code, language=language, signature=signature,
|
||||
when_to_use=when_to_use, repo=repo, path=path, symbol=symbol,
|
||||
locations=locations,
|
||||
),
|
||||
note_type=SNIPPET_NOTE_TYPE,
|
||||
tags=compose_tags(language, tags),
|
||||
|
||||
Reference in New Issue
Block a user