feat(explore): 3-pane tagging workspace — gallery | viewer | tag rail
Reworks Explore from "anchor + neighbour grid + cluster tag-gap rail" into a persistent 3-pane workspace that unfolds the image modal so you can tag while rabbit-holing (operator concept 2026-06-26): - LEFT neighbour grid (larger thumbs), click = walk; breadcrumb retained. - CENTER light viewer — reuses ImageCanvas + ImageMetaBar(:image) for the focused image; "Open full viewer" still launches the overlay modal. - RIGHT the modal's TagPanel, hosted on the anchor for modal-parity tagging (chips, autocomplete, suggestions + Accept, fandom-on-chip, T/"/" focus). Reuse without destabilising the audited modal store: TagPanel and SuggestionsPanel gain an optional `host` prop (default = modal store, so the image modal is unchanged); the explore store implements the same small tag-CRUD surface (current/currentImageId + reloadTags/addExistingTag/ removeTag/createAndAdd) over the anchor. ImageMetaBar gains an optional `image` prop for the same reason. Drops the mass/cluster tagger (TagGapPanel deleted; clusterIds/thumbById removed) — per-image tagging feeds the per-tag reference-embedding centroid better than bulk ops. Nav: keep the Explore tab but bare /explore now SEEDS a random image (GET /api/showcase?limit=1 → /explore/:id) so the tab kick-starts a rabbit hole; explicit meta.navOrder pins nav order (Explore after Gallery) since router.getRoutes() doesn't preserve declaration order. Note: the backend cluster tag-gaps route/service (#94a) is now frontend-orphaned — left in place; flag for a separate cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,12 +51,19 @@ import { useModalStore } from '../../stores/modal.js'
|
||||
import SuggestionsCategoryGroup from './SuggestionsCategoryGroup.vue'
|
||||
import AliasPickerDialog from './AliasPickerDialog.vue'
|
||||
|
||||
const props = defineProps({ imageId: { type: Number, required: true } })
|
||||
const props = defineProps({
|
||||
imageId: { type: Number, required: true },
|
||||
// The tagging host whose chip rail to refresh after an accept. Defaults to
|
||||
// the modal store (image modal); the Explore workspace passes its anchor host
|
||||
// so the same panel refreshes the right surface. See TagPanel.
|
||||
host: { type: Object, default: null },
|
||||
})
|
||||
// 'accepted' lets the parent return focus to the tag input after a suggestion is
|
||||
// applied (operator-asked 2026-06-08).
|
||||
const emit = defineEmits(['accepted'])
|
||||
const store = useSuggestionsStore()
|
||||
const modal = useModalStore()
|
||||
const modalStore = useModalStore()
|
||||
const host = props.host || modalStore
|
||||
|
||||
// 'artist' (FC-2d-vii-c) and 'copyright' (2026-06-01) retired as
|
||||
// suggestion categories. Only 'character' remains as a people-style
|
||||
@@ -84,7 +91,7 @@ watch(() => props.imageId, (id) => {
|
||||
async function onAccept(s) {
|
||||
try {
|
||||
await store.accept(s)
|
||||
await modal.reloadTags()
|
||||
await host.reloadTags()
|
||||
emit('accepted')
|
||||
} catch (e) {
|
||||
toast({ text: `Accept failed: ${e.message}`, type: 'error' })
|
||||
@@ -98,7 +105,7 @@ async function onAliasConfirm(canonicalTagId) {
|
||||
try {
|
||||
await store.aliasAccept(aliasTarget.value, canonicalTagId)
|
||||
aliasDialog.value = false
|
||||
await modal.reloadTags()
|
||||
await host.reloadTags()
|
||||
emit('accepted')
|
||||
} catch (e) {
|
||||
toast({ text: `Alias failed: ${e.message}`, type: 'error' })
|
||||
|
||||
Reference in New Issue
Block a user