Series manage redesign (FC-6.4) + migration/normalize hardening + UX fixes #84

Merged
bvandeusen merged 7 commits from dev into main 2026-06-07 21:17:58 -04:00
Owner

Second batch since the last main cut. All on dev at green CI (run #267, a00a278).

Migration safety

  • Advisory-locked migrationsalembic/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

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)
bvandeusen added 7 commits 2026-06-07 21:17:52 -04:00
fix(alembic): serialize concurrent migrators with an advisory lock
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m10s
7309d1d6d4
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>
chore: gitignore the .superpowers working dir
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 27s
CI / frontend-build (push) Successful in 40s
CI / integration (push) Successful in 3m5s
5bc8ef65ad
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(fandom): match change-fandom modal focus + Enter to FandomPicker
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 26s
CI / integration (push) Successful in 3m6s
aaa375654b
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>
fix(allowlist): lower default auto-apply threshold 0.95 → 0.90
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m7s
7daf90f41e
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>
fix(tags): rename-onto-existing in the image modal now merges, not errors
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 26s
CI / frontend-build (push) Successful in 32s
CI / integration (push) Successful in 3m7s
9770dd3474
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>
fix(tags): normalize task fails fast on lock + logs progress
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 26s
CI / integration (push) Successful in 3m5s
a00a2786e3
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>
bvandeusen merged commit 0666e15211 into main 2026-06-07 21:17:58 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledCurator#84