Summary
The third and final piece of the chat assistant decomposition, after the tool layer (PR #378) and streaming/rendering. These three items are grouped because each one composes the earlier two rather than standing alone.
Not yet specced — this issue captures the intent so it isn't lost.
Scope
Photo attachment in chat
Scan-to-Spool already reads a filament label from a photo, but that capability lives only in the intake dialog. You cannot drop a label photo into the chat drawer and say "add this one".
Reuses the existing vision path in spoolman/spoolintake.py (load_catalog, score_candidate, build_matches) rather than opening a second one. The natural flow: attach photo → extract → catalog_lookup for real density/diameter → create_filament confirm-card, which is exactly the chain the tool layer already supports.
Additionally depends on a vision-capable local model — see #380. No currently-installed model reports the vision capability, so this cannot be tested locally until that is resolved.
Bulk actions behind one grouped confirm-card
"Archive everything empty in shelf B" is currently one confirm-card per spool, if it works at all. This needs a grouped card that states the whole blast radius once — how many entities, which ones, what changes — and executes atomically on a single confirmation.
Depends on the card rendering from the streaming/rendering work; a grouped bulk card built on today's plain-text rendering would be unreadable.
Note the existing convention: every write previews before executing, require_write is re-checked at execution time, and an undo descriptor is returned where the change is cleanly reversible. A bulk action needs to decide what "undo" means for a partially-applied batch.
Proactive low-stock nudges
The assistant only ever speaks when spoken to. With find_filaments(low_stock_only=true) and get_usage_stats already in the tool layer, it could surface "you're nearly out of black PETG, and you're using ~400 g a month" without being asked.
Design questions this needs answering before implementation: where does a nudge appear (drawer badge? home page?), how often, and how a user turns it off. An assistant that interrupts is worse than one that waits.
Dependencies
Summary
The third and final piece of the chat assistant decomposition, after the tool layer (PR #378) and streaming/rendering. These three items are grouped because each one composes the earlier two rather than standing alone.
Not yet specced — this issue captures the intent so it isn't lost.
Scope
Photo attachment in chat
Scan-to-Spool already reads a filament label from a photo, but that capability lives only in the intake dialog. You cannot drop a label photo into the chat drawer and say "add this one".
Reuses the existing vision path in
spoolman/spoolintake.py(load_catalog,score_candidate,build_matches) rather than opening a second one. The natural flow: attach photo → extract →catalog_lookupfor real density/diameter →create_filamentconfirm-card, which is exactly the chain the tool layer already supports.Additionally depends on a vision-capable local model — see #380. No currently-installed model reports the
visioncapability, so this cannot be tested locally until that is resolved.Bulk actions behind one grouped confirm-card
"Archive everything empty in shelf B" is currently one confirm-card per spool, if it works at all. This needs a grouped card that states the whole blast radius once — how many entities, which ones, what changes — and executes atomically on a single confirmation.
Depends on the card rendering from the streaming/rendering work; a grouped bulk card built on today's plain-text rendering would be unreadable.
Note the existing convention: every write previews before executing,
require_writeis re-checked at execution time, and an undo descriptor is returned where the change is cleanly reversible. A bulk action needs to decide what "undo" means for a partially-applied batch.Proactive low-stock nudges
The assistant only ever speaks when spoken to. With
find_filaments(low_stock_only=true)andget_usage_statsalready in the tool layer, it could surface "you're nearly out of black PETG, and you're using ~400 g a month" without being asked.Design questions this needs answering before implementation: where does a nudge appear (drawer badge? home page?), how often, and how a user turns it off. An assistant that interrupts is worse than one that waits.
Dependencies