The owner could not tell from the UI what "Buzz Price Tracking" enabled, what the
"Price alert threshold" number meant, or what "Price Alerts" was alerting about.
That was not a copy problem: the implementation exposed our mechanism (a page
scrape) and our SQL predicates as the user's concepts. Two concrete defects came
from the same root:
- the alert population included versions the user already owns (neither the event
generator nor the query filtered on is_in_library; in the owner's library 28 of
52 gated versions were already downloaded, so most "alerts" were about files
already on disk, which cannot become cheaper *for them*);
- a threshold-filtered state list lived in a notification surface, so an empty
panel had three indistinguishable causes and read as a broken feature.
The information model is now the version plus ownership: cost is shown only where
a decision exists. Owned -> nothing. Not owned and free -> nothing. Not owned and
gated -> the price when it is known, `Paid` without a number when it is not, and
early access keeps its countdown because "free on <date>" decides between waiting
and paying. The numeric threshold has no place in that model: every decision is
categorical (wait / pay / skip), so the setting, the comparison and the whole
alert-state machine are gone.
- both alert-state columns are removed from the schema rather than left dead; a
database created by an unreleased build has them dropped on open (native
ALTER TABLE ... DROP COLUMN, guarded), which is a no-op for everyone else
- gate events are emitted only for versions the user does not have, and the
price-drop event goes with the threshold it belonged to
- both alert endpoints, PriceAlertsHandler and the service-registry adapter field
it needed are removed: events already reach the UI through the refresh response
- the bell tab, panel, CSS, both entry points, the unread watermark and their
locales are removed; the setting keeps only the enable flag and the refresh
interval and is framed as plumbing
- "Price unavailable" is replaced by `Paid`: the gate is certain from the public
API, only the number is best-effort, and that is our plumbing, not the user's
problem
Verified against a copy of the owner's real database: 52 gated versions ->
28 owned (now silent) + 24 the feature is actually about; the drop migration ran
and both removed endpoints 404.
P5a of docs/plans/paid-model-price-tracking.md: one surface that answers "what
got cheaper / became free", without a permanent button (the grid filter was
dropped by owner decision, so the panel carries the actions itself).
- price_alert_since records when an alert started, so the panel can say
"dropped 3 d ago" and count what is new since the user last looked; it is set
on the first sight of an already-cheap version, preserved while the alert
stands, and cleared when the price rises back above the threshold
- get_price_alerts() compares the threshold at read time (editing it takes
effect immediately, no refresh needed) and returns both kinds in one list;
model_type=None covers every type, which the shared update DB makes a single
query. "became free" needs no price data, so it is reported even while price
tracking is off
- GET /api/lm/price-alerts, registered once in MiscRoutes rather than per model
type, decorating rows best-effort with the local model name and file path from
the scanner indexes (a cold cache just omits them)
- a third tab in the notification bell: segments for under-threshold and
became-free, the three states (tracking off / nothing matching / stale), and
per-row actions (CivitAI always, Open when the model is local)
- two non-permanent entry points share one helper: the controls-bar updates
dropdown and the global context menu, whose label carries the unread count
- unread state stays client-side (localStorage watermark); the count is fetched
once on init and only when price tracking is enabled
- the per-type frontend client method is removed as dead code; the per-type
backend route stays for the companion extension
Replace the post-run toast cascade and the standalone L4 results modal
with a summary modal modeled on the batch download summary: 3-state
header, stat cards (matched / needs review / unresolved / errors),
an L4 review table with per-entry undo, and a copyable report. Wired
into the global, bulk and single-recipe rematch entries; complete
no-op runs keep the lightweight toast. Obsolete results-modal code,
styles and i18n keys are removed.
- Snapshot pre-rematch entry state (reconnectSnapshot) so rematched
entries can be undone via the existing restore flow
- Bulk missing-LoRA downloads mark unresolvable failures hash-invalid,
flipping those entries from download to reconnect candidacy
- Recipe modal always offers a reconnect action next to download for
missing LoRA entries
- Rematch runs collect an opt-in relaxed-matching choice (also reconnect
missing models by file name) via a pre-run options dialog on the
global, bulk and single-recipe entries
- L4 (filename-level) matches are listed in a results dialog with
per-entry undo
The recipe "Repair Metadata" action has been marked Deprecated in the UI
for a while and cannot reliably recover recipes imported from CivitAI URLs
whose REST meta has no resources/hashes and whose image has no embedded
metadata (e.g. CivitAI-only generation data). Drop the feature end to end.
Backend:
- remove repair routes (repair, cancel-repair, recipe/{id}/repair,
repair-bulk, repair-progress) and their handler mappings/methods
- remove RecipeScanner repair_all_recipes / repair_recipe_by_id /
_repair_single_recipe and REPAIR_VERSION
- remove WebSocketManager recipe-repair progress channel
- drop repair_version column from the persistent recipe cache
- rematch mutual-exclusion now only checks rematch
Frontend:
- remove repair entries from per-recipe, bulk and global context menus
- remove repairRecipe / repairSelectedRecipes / repairRecipes + cancelRepair
and the repairBulk API client method/endpoint
- drop recipe-repair i18n keys (synced across locales; doctor keys kept)
Tests/docs: delete test_recipe_repair.py, update scaffolding/routes/ws/
persistent-cache/integration tests and i18n guideline examples.
- group_by_model dedup now counts versions per group and attaches
version_count; respects update_flag_strategy (same_base) by
sub-grouping on base_model
- Card footer shows clickable 'x versions' link instead of version
name when grouped (hides HIGH/LOW badges); clicking triggers
View Local Versions without page reload
- Added 'Local Versions' sort option (versions_count), auto-hidden
when group_by_model is off
- Sort preference is saved/restored separately for normal and
grouped modes
- VLM flow (triggerVlmView, clearCustomFilter) uses resetAndReload()
via API instead of window.location.reload()
- Fixed cache mutation bug: version_count is now set on a shallow
copy, not the cached dict, preventing stale version_count leaking
into VLM responses
- i18n: all 9 locale files translated
Adds a 'Group by Model' toggle entry to the right-click global context
menu for quick access, complementing the existing setting in
Settings → Layout Settings. The menu item shows a checkmark indicator
reflecting the current state and immediately reloads the view on toggle.
Also fixes he.json translation that was mojibake (garbled characters).
Includes:
- Context menu HTML item with check-indicator
- JS toggle logic via settingsManager
- i18n for all 10 locales
- Hebrew translation fix
When force=true is passed via API, models in failed_models set are
re-downloaded instead of being skipped. On successful download, model is
removed from failed_models set.
This provides a manual batch repair mechanism for users when CivitAI
media server is temporarily down and causes empty folders.
Changes:
- Backend: Add force parameter to start_download(), _download_all_example_images(), _process_model()
- Backend: Skip failed_models check when force=true
- Backend: Remove model from failed_models on successful force retry
- Frontend: GlobalContextMenu now calls API with force=true directly
- Tests: Update mock to accept force parameter
- Add disabled and loading states for control group buttons with appropriate cursor and opacity styling
- Enhance dropdown toggle active state styling for update filter group
- Improve dropdown toggle layout with flex centering
- Add disabled state styling for dropdown items
- Refactor model update check to use shared helper function, removing redundant success handling and simplifying error flow
- Maintain existing functionality while improving user experience and code maintainability
Add a new "Check Model Updates" option to the global context menu that allows users to manually check for model updates. This includes:
- Adding refreshUpdates endpoint to API configuration
- Implementing checkModelUpdates method with proper loading states
- Adding internationalization support for update messages
- Handling success/error states with appropriate user feedback
- Automatically reloading models after update check completes
The feature provides users with manual control over update checks and improves visibility into model update availability.