feat(series): pending staging for add-from-post (#789 Phase 2)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m17s

Add-from-post no longer appends straight into the run — it STAGES the post's
pages as pending (per-page status; page_number NULL), grouped by source post,
so the operator drops junk (text-free alts, bumpers) and places the keepers
into the sequence with clean series-global numbering.

- migration 0048: series_page.status ('placed' default | 'pending') + nullable
  page_number.
- series_service: placed/pending split everywhere (list_pages returns the
  placed run + a `pending` section grouped by source post; reorder/cover/
  list_series operate on placed only); add_post stages pending; new
  place_pending(image_ids, before_image_id=None) flips pending→placed spliced
  before a page (or appended) and renumbers; junk removal reuses remove_images.
- api/tags: /add-post now returns staged count; new POST /series/<id>/pending/
  place.
- frontend: PostSeriesMenu navigates to the series after staging; seriesManage
  store surfaces `pending` + placePending; SeriesManageView gains a pending
  tray (per-post groups, place-all / place-one / drop-junk).
- tests: pending staging, place (append + insert-before), ignore-already-
  placed, drop-junk, route guard; updated add_post + match-accept expectations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:47:58 -04:00
parent 59746d213d
commit 7bb765b6ed
10 changed files with 521 additions and 42 deletions
+13 -2
View File
@@ -20,6 +20,7 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
const series = ref(null)
const pages = ref([]) // flat, ordered: [{image_id, page_number, stated_page, thumbnail_url, image_url, source_post}]
const dividers = ref([]) // [{id, anchor_image_id, page_number, title, stated_part}]
const pending = ref([]) // staged-from-post: [{post:{id,title}|null, pages:[{image_id, stated_page, thumbnail_url, image_url}]}]
const partGaps = ref([]) // [{after_divider_id, start, end}]
const pageCount = ref(0)
const picker = ref([]) // gallery scroll results
@@ -35,6 +36,7 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
series.value = body.series
pages.value = body.pages || []
dividers.value = body.dividers || []
pending.value = body.pending || []
partGaps.value = body.part_gaps || []
pageCount.value = pages.value.length
} finally {
@@ -105,6 +107,15 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
toast({ text: 'Chapter divider removed', type: 'success' })
}
// ---- pending staging (add-from-post) ----
async function placePending(imageIds, beforeImageId = null) {
await api.post(`/api/series/${tagId.value}/pending/place`, {
body: { image_ids: imageIds, before_image_id: beforeImageId }
})
await refresh()
toast({ text: 'Pages placed into the series', type: 'success' })
}
// ---- picker / add pages ----
async function loadPicker(reset = false) {
if (reset) { picker.value = []; pickerCursor.value = null }
@@ -132,10 +143,10 @@ export const useSeriesManageStore = defineStore('seriesManage', () => {
}
return {
tagId, series, pages, dividers, partGaps, pageCount,
tagId, series, pages, dividers, pending, partGaps, pageCount,
picker, pickerCursor, pickerSelection, loading,
load, refresh, dividerAt, partGapAfter,
reorder, remove, setCover,
reorder, remove, setCover, placePending,
createDivider, renameDivider, setDividerPart, deleteDivider,
loadPicker, togglePick, addSelected
}