feat: store-level "Group bursts" toggle + idle-window field

Adds group_burst (0/1) + burst_window_sec to the Store type and the
StoreModal editor. Drives finbot's Amazon burst-grouping: when ON, finbot
collapses rapid same-merchant charge bursts into one Firefly transaction
group; the optional idle window (blank = 180s) closes the group.

Backend INSERT/UPDATE carry the fields; GET is SELECT * so reads are free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maddox 2026-06-16 17:40:39 -04:00
parent 5fc745edd3
commit 9f901ec50d
2 changed files with 42 additions and 0 deletions

View file

@ -23,6 +23,8 @@ export function StoreModal({ store, categories, availableTags, onSave, onClose }
tagList: parseTags(store?.tags ?? ''), tagList: parseTags(store?.tags ?? ''),
route: (store?.route ?? 'auto') as Route, route: (store?.route ?? 'auto') as Route,
display_order: store?.display_order ?? 0, display_order: store?.display_order ?? 0,
group_burst: store?.group_burst ?? 0,
burst_window_sec: store?.burst_window_sec ?? null,
}) })
// Keep category default once categories load // Keep category default once categories load
@ -137,6 +139,41 @@ export function StoreModal({ store, categories, availableTags, onSave, onClose }
/> />
</div> </div>
{/* Burst grouping */}
<div className="space-y-1.5">
<label className="text-xs font-medium text-slate-400 uppercase tracking-wider">
Burst Grouping
</label>
<button
type="button"
onClick={() => set('group_burst', form.group_burst ? 0 : 1)}
className={`w-full flex items-center justify-between rounded-xl px-3.5 py-2.5 text-sm border transition-colors ${
form.group_burst
? 'bg-blue-500/15 border-blue-500/50 text-slate-100'
: 'bg-surface-800 border-white/8 text-slate-400 hover:text-slate-300'
}`}
>
<span>Group rapid same-merchant charges</span>
<span className={`text-xs font-semibold ${form.group_burst ? 'text-blue-400' : 'text-slate-500'}`}>
{form.group_burst ? 'ON' : 'OFF'}
</span>
</button>
{form.group_burst ? (
<div className="flex items-center gap-2 pt-1">
<label className="text-xs text-slate-500 whitespace-nowrap">Idle window (sec)</label>
<input
type="number"
min={10}
value={form.burst_window_sec ?? ''}
onChange={e => set('burst_window_sec', e.target.value === '' ? null : Number(e.target.value))}
placeholder="180"
className="w-24 bg-surface-800 border border-white/8 rounded-lg px-2.5 py-1.5 text-sm text-slate-200 placeholder:text-slate-600 focus:outline-none focus:border-blue-500/60"
/>
<span className="text-xs text-slate-600">blank = 180s</span>
</div>
) : null}
</div>
{/* Actions */} {/* Actions */}
<div className="flex gap-2 pt-1"> <div className="flex gap-2 pt-1">
<button <button

View file

@ -7,6 +7,11 @@ export interface Store {
tags: string tags: string
route: Route route: Route
display_order: number display_order: number
// Burst-grouping: when 1, finbot collapses same-merchant charge bursts
// (e.g. Amazon Subscribe & Save) into one Firefly transaction group.
// burst_window_sec = idle gap that closes the group (NULL = global 180s).
group_burst: number
burst_window_sec: number | null
created_at: string created_at: string
} }