Second batch since the last main cut. All on dev at green CI (run #267, a00a278).
Migration safety
Advisory-locked migrations — alembic/env.py wraps run_migrations() in a pg_advisory_xact_lock acquired before the version read. Every web replica migrates on boot, so under docker stack deploy two replicas raced 0040 in prod (duplicate CREATE TABLE → one replica crash-looped on AdminShutdown). The first replica now migrates; siblings block then no-op at head.
FC-6.4 — series manage view redesign
Operator feedback: thumbnails too small to order, no way to mark "this is Part 2", busy two-pane picker.
Full-width parts, each a card with a big page grid (whole pages visible) + drag-reorder.
Editable Part # backed by new series_chapter.stated_part (alembic 0042), separate from the auto-managed chapter_number so reorder/delete can't wipe a hand-set number; "Missing Part N" hints.
Each part labels its source post and shows the printed-page range.
Picker demoted to an on-demand right slide-over; part actions in an overflow ⋮.
Fixes
Change-fandom modal now matches FandomPicker — focus lands in the field, Enter saves instead of re-opening the dropdown, Tab to the new-fandom field.
Rename-onto-existing in the image modal now merges (was a leftover "lands in FC-2c" stub) — shows the same merge confirmation the Tags view uses.
Auto-apply default lowered 0.95 → 0.90 for newly-accepted tags (operator evidence; existing entries unchanged, no migration).
normalize_tags task fails fast on a lock (lock_timeout=30s) + logs progress — it had run to the 40-min hard limit with no logs when its series_page repoint blocked behind the wedged 0040 migration.
Schema: alembic head moves to 0042 (one new migration; applied with 0041 behind the advisory lock).
Second batch since the last main cut. All on `dev` at green CI (run #267, `a00a278`).
## Migration safety
- **Advisory-locked migrations** — `alembic/env.py` wraps `run_migrations()` in a `pg_advisory_xact_lock` acquired before the version read. Every web replica migrates on boot, so under `docker stack deploy` two replicas raced 0040 in prod (duplicate CREATE TABLE → one replica crash-looped on `AdminShutdown`). The first replica now migrates; siblings block then no-op at head.
## FC-6.4 — series manage view redesign
Operator feedback: thumbnails too small to order, no way to mark "this is Part 2", busy two-pane picker.
- Full-width parts, each a card with a **big page grid** (whole pages visible) + drag-reorder.
- Editable **Part #** backed by new `series_chapter.stated_part` (alembic **0042**), separate from the auto-managed `chapter_number` so reorder/delete can't wipe a hand-set number; "Missing Part N" hints.
- Each part labels its **source post** and shows the printed-page range.
- Picker demoted to an on-demand right **slide-over**; part actions in an overflow ⋮.
## Fixes
- **Change-fandom modal** now matches FandomPicker — focus lands in the field, Enter saves instead of re-opening the dropdown, Tab to the new-fandom field.
- **Rename-onto-existing** in the image modal now **merges** (was a leftover "lands in FC-2c" stub) — shows the same merge confirmation the Tags view uses.
- **Auto-apply default** lowered 0.95 → 0.90 for newly-accepted tags (operator evidence; existing entries unchanged, no migration).
- **normalize_tags task** fails fast on a lock (`lock_timeout=30s`) + logs progress — it had run to the 40-min hard limit with no logs when its `series_page` repoint blocked behind the wedged 0040 migration.
Schema: alembic head moves to **0042** (one new migration; applied with 0041 behind the advisory lock).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Every web replica runs 'alembic upgrade head' in its entrypoint, so under
docker stack deploy two replicas can boot at once and race the same DDL —
0040 raced in prod (operator-flagged 2026-06-07): one backend wedged on the
series_page lock while a second tried to re-CREATE series_chapter, and the
loser died with AdminShutdown, crash-looping the web service.
Wrap run_migrations() in a transaction-scoped pg_advisory_xact_lock acquired
BEFORE the version table is read. The first replica to reach it migrates and
holds the lock for the whole upgrade; siblings block, then find the version
already at head and apply nothing. Works regardless of replica count and
needs no Swarm depends_on ordering (which stack deploy ignores anyway).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Operator feedback: thumbnails too small to judge order, no obvious way to mark
'this installment is Part 2', and the permanent two-pane picker was busy and
competed with the ordering work.
- Full-width parts, each a card with a big page grid (150px, contain so whole
pages are visible) and drag-to-reorder; positional page number as a badge.
- Editable Part # (hero field) backed by new series_chapter.stated_part —
separate from the auto-managed chapter_number, mirroring the page_number vs
stated_page split so reorder/delete renumbering can't wipe a hand-set part.
Missing-Part hints when consecutive parts' stated_part jump >1.
- Each part labels its source post (derived from pages' primary_post_id) and
shows the printed-page range with clear labels.
- Picker demoted to an on-demand right slide-over ('Add pages') with a target-
part selector; part actions (move/merge/delete) collapsed into an overflow ⋮.
alembic 0042 adds series_chapter.stated_part (nullable int).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 'Fandom for <character>' dialog (FandomSetDialog) used plain autofocus and
had no Enter handling, so Enter re-opened the dropdown instead of submitting —
the same bug FandomPicker already fixed. Mirror that flow: parent v-dialogs
focus the field via @after-enter→focusSearch (reliable past the focus-trap);
capture-phase Enter Saves the changed selection instead of re-opening the menu;
Tab jumps to the new-fandom field; creating a fandom returns focus to the
dropdown so a single Enter saves it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Operator evidence 2026-06-07: 0.95 was too strict, skipping confident-enough
auto-applications of accepted tags. Newly-accepted tags now allowlist at 0.90;
existing entries keep their stored value and per-tag thresholds stay tunable in
the allowlist table. No migration — min_confidence has no DB server_default, so
the Python insert default governs new rows only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The image-modal tag kebab's rename dialog still showed a leftover stub
('Merging two tags into one lands in FC-2c') on a name collision, dead-ending
the operator. The merge machinery has existed for a while — the Tags view
already resolves rename collisions this way. Wire TagRenameDialog to it: on the
409 collision hint, show the same merge confirmation FandomSetDialog uses
(target name, image associations moved, alias kept) and POST /api/tags/<id>/merge
into the existing tag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
normalize_tags_task ran to the 40-min hard limit with zero logs (operator-
flagged 2026-06-07). Cause: a per-group merge repoints series_page (via
_repoint_series_pages); during the wedged 0040 migration that held ACCESS
EXCLUSIVE on series_page, the merge's UPDATE blocked on that lock. The time-box
check is at the top of the group loop, so a statement blocked mid-group never
yields back to it — the task sat until the Celery hard kill. No logs because the
only log fired per *finished* group.
- Set lock_timeout=30s on the normalize session (opt-in server_settings on the
async factory). A blocked merge now raises, the per-group handler rolls back +
counts an error, and the loop continues — one stuck group can't strand the
chunk, and the budget checkpoint stays effective.
- Log group count at start + a heartbeat every 25 groups, so a long/slow run is
diagnosable instead of silent.
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.
Second batch since the last main cut. All on
devat green CI (run #267,a00a278).Migration safety
alembic/env.pywrapsrun_migrations()in apg_advisory_xact_lockacquired before the version read. Every web replica migrates on boot, so underdocker stack deploytwo replicas raced 0040 in prod (duplicate CREATE TABLE → one replica crash-looped onAdminShutdown). The first replica now migrates; siblings block then no-op at head.FC-6.4 — series manage view redesign
Operator feedback: thumbnails too small to order, no way to mark "this is Part 2", busy two-pane picker.
series_chapter.stated_part(alembic 0042), separate from the auto-managedchapter_numberso reorder/delete can't wipe a hand-set number; "Missing Part N" hints.Fixes
lock_timeout=30s) + logs progress — it had run to the 40-min hard limit with no logs when itsseries_pagerepoint blocked behind the wedged 0040 migration.Schema: alembic head moves to 0042 (one new migration; applied with 0041 behind the advisory lock).
🤖 Generated with Claude Code
Operator feedback: thumbnails too small to judge order, no obvious way to mark 'this installment is Part 2', and the permanent two-pane picker was busy and competed with the ordering work. - Full-width parts, each a card with a big page grid (150px, contain so whole pages are visible) and drag-to-reorder; positional page number as a badge. - Editable Part # (hero field) backed by new series_chapter.stated_part — separate from the auto-managed chapter_number, mirroring the page_number vs stated_page split so reorder/delete renumbering can't wipe a hand-set part. Missing-Part hints when consecutive parts' stated_part jump >1. - Each part labels its source post (derived from pages' primary_post_id) and shows the printed-page range with clear labels. - Picker demoted to an on-demand right slide-over ('Add pages') with a target- part selector; part actions (move/merge/delete) collapsed into an overflow ⋮. alembic 0042 adds series_chapter.stated_part (nullable int). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>The image-modal tag kebab's rename dialog still showed a leftover stub ('Merging two tags into one lands in FC-2c') on a name collision, dead-ending the operator. The merge machinery has existed for a while — the Tags view already resolves rename collisions this way. Wire TagRenameDialog to it: on the 409 collision hint, show the same merge confirmation FandomSetDialog uses (target name, image associations moved, alias kept) and POST /api/tags/<id>/merge into the existing tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>