feat(avocet): Pinia label store with queue, lastAction, easter egg counter

This commit is contained in:
pyr0ball 2026-03-03 15:54:44 -08:00
parent 2abda18b92
commit 209f49f7ea
2 changed files with 115 additions and 0 deletions

View file

@ -0,0 +1,62 @@
// src/stores/label.test.ts
import { setActivePinia, createPinia } from 'pinia'
import { useLabelStore } from './label'
import { beforeEach, describe, it, expect } from 'vitest'
const MOCK_ITEM = {
id: 'abc', subject: 'Test', body: 'Body', from: 'a@b.com',
date: '2026-03-01', source: 'imap:test',
}
describe('label store', () => {
beforeEach(() => setActivePinia(createPinia()))
it('starts with empty queue', () => {
const store = useLabelStore()
expect(store.queue).toEqual([])
expect(store.current).toBeNull()
})
it('current returns first item', () => {
const store = useLabelStore()
store.queue = [MOCK_ITEM]
expect(store.current).toEqual(MOCK_ITEM)
})
it('removeCurrentFromQueue removes first item', () => {
const store = useLabelStore()
store.queue = [MOCK_ITEM, { ...MOCK_ITEM, id: 'def' }]
store.removeCurrentFromQueue()
expect(store.queue[0].id).toBe('def')
})
it('tracks lastAction', () => {
const store = useLabelStore()
store.queue = [MOCK_ITEM]
store.setLastAction('label', MOCK_ITEM, 'interview_scheduled')
expect(store.lastAction?.type).toBe('label')
expect(store.lastAction?.label).toBe('interview_scheduled')
})
it('incrementLabeled increases sessionLabeled', () => {
const store = useLabelStore()
store.incrementLabeled()
store.incrementLabeled()
expect(store.sessionLabeled).toBe(2)
})
it('restoreItem adds to front of queue', () => {
const store = useLabelStore()
store.queue = [{ ...MOCK_ITEM, id: 'def' }]
store.restoreItem(MOCK_ITEM)
expect(store.queue[0].id).toBe('abc')
expect(store.queue[1].id).toBe('def')
})
it('clearLastAction nulls lastAction', () => {
const store = useLabelStore()
store.setLastAction('skip', MOCK_ITEM)
store.clearLastAction()
expect(store.lastAction).toBeNull()
})
})

53
web/src/stores/label.ts Normal file
View file

@ -0,0 +1,53 @@
// src/stores/label.ts
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export interface QueueItem {
id: string
subject: string
body: string
from: string
date: string
source: string
}
export interface LastAction {
type: 'label' | 'skip' | 'discard'
item: QueueItem
label?: string
}
export const useLabelStore = defineStore('label', () => {
const queue = ref<QueueItem[]>([])
const totalRemaining = ref(0)
const lastAction = ref<LastAction | null>(null)
const sessionLabeled = ref(0) // for easter eggs
const current = computed(() => queue.value[0] ?? null)
function removeCurrentFromQueue() {
queue.value.shift()
}
function setLastAction(type: LastAction['type'], item: QueueItem, label?: string) {
lastAction.value = { type, item, label }
}
function clearLastAction() {
lastAction.value = null
}
function restoreItem(item: QueueItem) {
queue.value.unshift(item)
}
function incrementLabeled() {
sessionLabeled.value++
}
return {
queue, totalRemaining, lastAction, sessionLabeled, current,
removeCurrentFromQueue, setLastAction, clearLastAction,
restoreItem, incrementLabeled,
}
})