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:
@@ -21,11 +21,11 @@ const canWrite = computed(() => {
|
||||
return p === undefined || p === "owner" || p === "edit" || p === "admin";
|
||||
});
|
||||
|
||||
const locationParts = computed(() => {
|
||||
const s = snippet.value?.snippet;
|
||||
if (!s) return [];
|
||||
return [s.repo, s.path, s.symbol].filter((p) => p && p.trim());
|
||||
});
|
||||
const locations = computed(() => snippet.value?.snippet.locations ?? []);
|
||||
|
||||
function locParts(loc: { repo: string; path: string; symbol: string }): string[] {
|
||||
return [loc.repo, loc.path, loc.symbol].filter((p) => p && p.trim());
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
@@ -92,10 +92,12 @@ async function confirmDelete() {
|
||||
<dt>Signature</dt>
|
||||
<dd><code>{{ snippet.snippet.signature }}</code></dd>
|
||||
</template>
|
||||
<template v-if="locationParts.length">
|
||||
<dt>Location</dt>
|
||||
<dd class="location">
|
||||
<code v-for="(p, i) in locationParts" :key="i">{{ p }}</code>
|
||||
<template v-if="locations.length">
|
||||
<dt>{{ locations.length > 1 ? "Locations" : "Location" }}</dt>
|
||||
<dd class="location-list">
|
||||
<div v-for="(loc, i) in locations" :key="i" class="location">
|
||||
<code v-for="(p, j) in locParts(loc)" :key="j">{{ p }}</code>
|
||||
</div>
|
||||
</dd>
|
||||
</template>
|
||||
<template v-if="snippet.snippet.language">
|
||||
@@ -242,6 +244,11 @@ async function confirmDelete() {
|
||||
border-radius: var(--radius-sm);
|
||||
word-break: break-all;
|
||||
}
|
||||
.location-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.location {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user