Batch of CI-green dev work (HEAD 3f6ea60, run #481 all green). No new alembic migrations — head remains 0035.
Gallery
Filter Phase 2 — faceted refine panel._apply_scope/_parse_filters gain platform/untagged/no_artist/date_from/date_to (AND-composed, threaded through scroll/timeline/jump). New GalleryService.facets() + GET /api/gallery/facets: live counts scoped to the current filter, per-group "minus-self", COUNT(DISTINCT) platform incl. a null "unsourced" bucket (UNSOURCED_PLATFORM). UI: Refine ▾ toggle in GalleryFilterBar (sticky hazey wrapper now wraps bar+panel) + new GalleryFacetPanel.vue; store loadFacets() + shared cloneFilter/filterToQuery.
Filter bar styling (earlier on dev): hazey obsidian + backdrop-blur matching TopNav, flush at top:64px.
Showcase
Buffered cascade (earlier on dev) + decode-gate: each tile now reveals only once its thumbnail is fully decoded (new utils/preloadImage.js; producer preloads ahead, consumer awaits decode) — fully-loaded, one at a time. Removed MasonryGrid's per-index animation-delay that compounded on the insert cadence and dragged/desynced the cascade.
Run the throwaway CI Postgres with fsync/synchronous_commit/full_page_writes OFF (runtime ALTER SYSTEM + pg_reload_conf(), superuser, pre-migrate, non-fatal): integration test execution ~13min → ~45s.
Collapsed the 3 integration shards into one integration job (6 CI jobs → 4, two runner slots freed).
Batch of CI-green dev work (HEAD `3f6ea60`, run #481 all green). No new alembic migrations — head remains 0035.
## Gallery
- **Filter Phase 2 — faceted refine panel.** `_apply_scope`/`_parse_filters` gain `platform`/`untagged`/`no_artist`/`date_from`/`date_to` (AND-composed, threaded through scroll/timeline/jump). New `GalleryService.facets()` + `GET /api/gallery/facets`: live counts scoped to the current filter, per-group "minus-self", `COUNT(DISTINCT)` platform incl. a null "unsourced" bucket (`UNSOURCED_PLATFORM`). UI: `Refine ▾` toggle in `GalleryFilterBar` (sticky hazey wrapper now wraps bar+panel) + new `GalleryFacetPanel.vue`; store `loadFacets()` + shared `cloneFilter`/`filterToQuery`.
- **Filter bar styling** (earlier on dev): hazey obsidian + backdrop-blur matching TopNav, flush at top:64px.
## Showcase
- **Buffered cascade** (earlier on dev) + **decode-gate**: each tile now reveals only once its thumbnail is fully decoded (new `utils/preloadImage.js`; producer preloads ahead, consumer awaits decode) — fully-loaded, one at a time. Removed MasonryGrid's per-index `animation-delay` that compounded on the insert cadence and dragged/desynced the cascade.
## DB maintenance
- Weekly `vacuum_analyze` Beat task + Settings→Maintenance "Run VACUUM ANALYZE now" + per-table bloat readout (`/api/admin/maintenance/db-stats|vacuum`).
## CI performance
- Run the throwaway CI Postgres with **fsync/synchronous_commit/full_page_writes OFF** (runtime `ALTER SYSTEM` + `pg_reload_conf()`, superuser, pre-migrate, non-fatal): integration test execution ~13min → ~45s.
- **Collapsed the 3 integration shards into one `integration` job** (6 CI jobs → 4, two runner slots freed).
- Session-scoped teardown engine (hygiene, ≈0 measured).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The cascade "burped" — chunks appeared unevenly — because the old pipeline
coupled display to fetch timing: it trickled each batch right after its
fetch and assumed the next round-trip would land inside the ~240ms trickle
window. When a fetch ran long (TABLESAMPLE hits random, sometimes-cold
blocks; RTT jitter) the animation starved, then a clump burst in.
Decouple the two:
- Producer (_fill) races ahead fetching batches into a buffer up to a
target depth, refilling when it dips below BUFFER_MIN.
- Consumer (_drain) reveals one item every CADENCE_MS regardless of when
fetches land; it only waits if the buffer genuinely starves.
A small PRIME buffer precedes the drain so it doesn't starve at the front;
the buffer (BUFFER_MIN×CADENCE runway) absorbs per-fetch jitter so images
appear at an even pace. Public store API (loadInitial/shuffle/fetchPage/
images/loading/hasMore/isEmpty) unchanged — ShowcaseView/MasonryGrid need
no change.
Test (fake timers): fire-order + dedup, one-item-per-cadence rate limit,
empty-library flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Filter bar gets the same obsidian translucent + backdrop-blur as the
TopNav so the two read as one piece of chrome.
- margin-top:-8px cancels the v-container's pt-2 so the bar sits flush at
64px even at scroll 0 — fixes the gap/separation when scrolled to top.
- Inputs/toggles get a more-opaque backing so they stay legible on the haze.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The TABLESAMPLE showcase reads physical blocks (bloat-sensitive), and the
periodic prune/backfill/recovery tasks churn dead tuples faster than
autovacuum always keeps up — so explicit maintenance earns its keep here.
- tasks.maintenance.vacuum_analyze: VACUUM (ANALYZE) over high-churn tables
(VACUUM_TABLES) on an AUTOCOMMIT connection (VACUUM can't run in a txn).
Scheduled weekly via Beat; also operator-triggerable.
- _sync_engine.get_sync_engine(): expose the process engine for the
autocommit connection.
- GET /api/admin/maintenance/db-stats: per-table n_live/n_dead/dead_pct +
last (auto)vacuum/analyze from pg_stat_user_tables — visibility, not a
black box.
- POST /api/admin/maintenance/vacuum: enqueue the task on demand.
Tests: vacuum task runs + reports tables; db-stats shape; trigger queues.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Settings → Maintenance gains a "Database maintenance" card: a "Run VACUUM
ANALYZE now" button (enqueues the maintenance task) plus a per-table bloat
readout (live/dead/dead%/last vacuum) from /api/admin/maintenance/db-stats.
- dbMaintenance store (loadStats / runVacuum) + test.
- Fix ruff I001: combine the two _sync_engine imports onto one line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Image viewer (#609):
- The next (▶) arrow was offset from the viewport edge (right:16px) so it
floated over the 320px metadata side panel. Offset it off a shared
--fc-side-w var so it sits at the image's right edge instead; full-width
again below 900px when the panel stacks under the image.
- Arrow nav was fully disabled whenever a text field was focused. Now it
yields to the caret ONLY when the field has text; an empty tag-entry field
still navigates ←/→. Extracted to utils/textEntry.js (arrowNavAllowed).
ESC behaviour unchanged (already closes the modal, overlay-aware).
Test: arrowNavAllowed — empty/non-text → navigate, text present → don't.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the composable gallery filter with platform / untagged / no_artist /
date_from / date_to, AND-composed with the existing tag/artist/media/sort
params and threaded through scroll, timeline, and jump_cursor.
Add GalleryService.facets() + GET /api/gallery/facets returning live counts
scoped to the current filter with per-group minus-self semantics: platform
counts (COUNT(DISTINCT image) incl. a null unsourced bucket), curation-flag
counts (untagged / no_artist), and effective_date min/max bounds. The
UNSOURCED_PLATFORM sentinel makes filesystem-imported content reachable via
the platform facet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a 'Refine ▾' toggle to the gallery filter bar that expands a full-width
GalleryFacetPanel below it, inside the same sticky hazey chrome. The panel
offers platform chips (with live counts + a 'No platform' unsourced bucket),
two count-badged curation-flag toggles (Untagged / No artist), and a from/to
date range bounded by the facet min/max.
Store gains the platform/untagged/no_artist/date_from/date_to filter params
(URL-mirrored, AND-composed) and a panel-gated, single-flighted loadFacets()
that fetches /api/gallery/facets scoped to the active filter. Shared
cloneFilter/filterToQuery helpers keep the bar and panel writing one URL
format. The panel auto-opens on deep-link when refine filters are present and
refetches counts (debounced) on every filter change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite facets() common/plat_scope as dict literals (C408). Open the refine
panel via a watch on hasRefineFilters rather than reading filter state at
bar-setup time — the parent applies the URL query in its onMounted, after the
bar child has set up, so the initial read was always the default (empty) state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The buffered cascade revealed tiles on an 80ms timer regardless of image
load, so the flip-in animation played on a gray placeholder and the thumbnail
popped in afterward. Worse, MasonryGrid ALSO applied a per-index
animation-delay (index×70ms) that compounded on top of the insert cadence,
so the cascade visibly dragged and desynced as it grew.
Now the producer preloads each queued thumbnail (decode pipelined ahead) and
the consumer awaits that decode before pushing the item — every tile animates
in fully loaded, strictly one at a time. Drop the compounding CSS stagger;
the store's one-item-at-a-time push is the sole pacer, so each tile animates
the instant it mounts. New utils/preloadImage.js (load+decode+timeout gate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The autouse integration teardown created a fresh SQLAlchemy engine + Postgres
connection for EVERY test, then disposed it — --durations showed the 15
slowest ops in both long shards were all ~1.5-2s teardowns (the connect+SCRAM
handshake, not test logic). Hoist the truncate engine to a session-scoped,
pool_pre_ping'd fixture so the pooled connection is reused across teardowns;
the TRUNCATE+restore still runs per test, so isolation is unchanged. Lazy
create_engine means the no-DB unit job instantiates but never connects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Option 1 (pooling the teardown connection) left teardowns at ~1.5-2s/test, so
the cost is the per-test TRUNCATE's commit forcing an fsync, not the connect
handshake. Each shard now ALTER SYSTEM SETs fsync/synchronous_commit/
full_page_writes off + pg_reload_conf() right after deps install, before
alembic — sighup/user-context GUCs apply with no restart. The DB is ephemeral
(rebuilt per run) so fsync-off is safe; the step is non-fatal so a perms
surprise can't red a shard. Speeds up every test's commit (setup inserts +
the teardown TRUNCATE), stacking on the pooled engine from the prior commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With fsync-off the whole integration suite runs in ~45s (was ~13min across
shards), so the 3-way split only triplicated the ~2min fixed overhead
(container + install + migrate) and consumed 3 of 6 runner slots for no
wall-clock gain. Merge intapi/intimp/intcore into one `integration` job:
spin up once, install once, migrate once, run `pytest -m integration` over
the whole suite. Frees 2 runner slots (6 jobs -> 4) and drops ~140 lines of
near-duplicate YAML.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Batch of CI-green dev work (HEAD
3f6ea60, run #481 all green). No new alembic migrations — head remains 0035.Gallery
_apply_scope/_parse_filtersgainplatform/untagged/no_artist/date_from/date_to(AND-composed, threaded through scroll/timeline/jump). NewGalleryService.facets()+GET /api/gallery/facets: live counts scoped to the current filter, per-group "minus-self",COUNT(DISTINCT)platform incl. a null "unsourced" bucket (UNSOURCED_PLATFORM). UI:Refine ▾toggle inGalleryFilterBar(sticky hazey wrapper now wraps bar+panel) + newGalleryFacetPanel.vue; storeloadFacets()+ sharedcloneFilter/filterToQuery.Showcase
utils/preloadImage.js; producer preloads ahead, consumer awaits decode) — fully-loaded, one at a time. Removed MasonryGrid's per-indexanimation-delaythat compounded on the insert cadence and dragged/desynced the cascade.DB maintenance
vacuum_analyzeBeat task + Settings→Maintenance "Run VACUUM ANALYZE now" + per-table bloat readout (/api/admin/maintenance/db-stats|vacuum).CI performance
ALTER SYSTEM+pg_reload_conf(), superuser, pre-migrate, non-fatal): integration test execution ~13min → ~45s.integrationjob (6 CI jobs → 4, two runner slots freed).🤖 Generated with Claude Code