feat(fc2a): add tag store, TagAutocomplete, and FandomPicker

TagAutocomplete: kind selector + debounced (200ms) search + keyboard nav
(up/down/enter/esc). When the operator types a character that doesn't
exist, the create flow first opens FandomPicker so the new character tag
lands with a valid fandom_id (otherwise the API rejects it).

FandomPicker lists existing fandoms (cached after first load) with an
inline create-new affordance, so the operator never has to leave the modal
to manage fandom hierarchies.

Tag store also exposes a kind→icon→color mapping that the TagPanel reuses
in Task 22 — keeping chip colors visually distinct per kind without per-app
accent collisions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 12:34:17 -04:00
parent 0faeebf3aa
commit 89fee260a6
3 changed files with 241 additions and 0 deletions
@@ -0,0 +1,48 @@
<template>
<v-card>
<v-card-title>Pick a fandom</v-card-title>
<v-card-text>
<v-autocomplete
v-model="selectedId"
:items="store.fandomCache"
:item-title="(f) => f.name"
:item-value="(f) => f.id"
label="Fandom" clearable density="compact"
/>
<v-divider class="my-3" />
<p class="text-caption mb-2">Or create a new fandom:</p>
<div class="d-flex" style="gap: 8px;">
<v-text-field v-model="newName" placeholder="New fandom name" density="compact" hide-details />
<v-btn :disabled="!newName.trim()" rounded="pill" @click="onCreate">Create</v-btn>
</div>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="$emit('cancel')">Cancel</v-btn>
<v-btn :disabled="!selectedId" color="primary" rounded="pill" @click="onConfirm">Use this fandom</v-btn>
</v-card-actions>
</v-card>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useTagStore } from '../../stores/tags.js'
const emit = defineEmits(['confirm', 'cancel'])
const store = useTagStore()
const selectedId = ref(null)
const newName = ref('')
onMounted(() => { if (store.fandomCache.length === 0) store.loadFandoms() })
async function onCreate() {
const f = await store.createFandom(newName.value.trim())
selectedId.value = f.id
newName.value = ''
}
function onConfirm() {
const f = store.fandomCache.find(x => x.id === selectedId.value)
if (f) emit('confirm', f)
}
</script>