feat: auto-sequence suggestion for duplicate titles (T425984)

Summary

  • Title cell editor surfaces a "Convert to sequence — Add #" inline suggestion when the typed title collides with another stash row OR a Commons file the current user previously published. Accepting rewrites every matching stash row's title to the literal placeholder <basename> #.
  • New src/api/sequence.js resolves placeholders at publish time: queries Commons for the user's existing <basename> N.<ext> files (via aiprefix + per-row user filter), finds max N, and assigns each placeholder row N+1, N+2, … in queue order. If no prior owned sequence exists, numbering starts at 1.
  • validateTitleLocal allows the <basename> # form as a special case (other uses of # still fail). The Commons uniqueness check skips placeholder titles entirely. Cache-key prefix bumped to -v2 because the API call now also fetches the uploader's username so the editor can distinguish own- from someone-else's collisions (suggestion is suppressed for the latter, per spec).
  • Single-publish and bulk-publish modals show resolved <basename> N in their previews and pass the resolved title through to publishOne via a new resolvedTitle option, which also patches buildWikitext so {{Artwork}} / {{Photograph}} / {{Book}} render the final value rather than the placeholder.

Phabricator

  • T425984 — Auto-sequence suggestion: convert duplicate titles into a # numbered sequence (own files only)

Test plan

Cross-stash collision

  • Add two stash rows with the same title (e.g. both Sunset over Maastricht). Open the title editor on either row.
  • Inline suggestion appears: "2 stash rows share this name. Convert to a numbered sequence?" with an "Add #" button.
  • Click the button: both row titles change to Sunset over Maastricht #. Status shows "Sequence placeholder — the publish step will assign the next number."

Cross-Commons (own-file) collision

  • Edit a stash row's title to match a file you've previously published (e.g. an existing File:Maastricht railway 1.jpg).
  • After the uniqueness check resolves, the cell shows a yellow "You already published File:… — convert to a numbered sequence below, or pick a different title" status, and the suggestion strip surfaces below.
  • Accepting rewrites the row to <basename> #.

Cross-Commons (someone else's file) — should NOT offer

  • Edit a stash row to match a Commons file uploaded by someone else.
  • The cell shows the standard red "Already exists on Commons — pick a different title" error. No sequence suggestion appears.

Publish-time resolution (single)

  • Stash row with title Foo #. Open the publish modal.
  • "Will be published as" shows a spinner labelled "Resolving sequence number…", then the resolved name (e.g. File:Foo 8.jpg).
  • Wikitext preview shows Foo 8 (not Foo #) inside |title= if the template uses it.
  • Click Publish — file lands on Commons as File:Foo 8.jpg.

Publish-time resolution (bulk)

  • Three stash rows all titled Bar #. Open bulk-publish modal.
  • Top of the queue shows "Resolving sequence numbers… (3 placeholder rows)" briefly, then "Sequence resolved for 3 placeholder rows".
  • Each queue row shows the resolved title (e.g. Bar 1, Bar 2, Bar 3 if no prior owned sequence; or Bar 5, Bar 6, Bar 7 if Commons has Bar 4 from this user).
  • Hovering a row shows "Will publish as File:Bar N.jpg" tooltip.
  • Publish all → files land in sequence on Commons.

Edge cases

  • Foo # placeholder + sibling row that's NOT a placeholder but DOES match Foo → editor shows the suggestion only when the non-placeholder row is being edited; the placeholder row already shows the "Sequence placeholder" status (no re-suggest).
  • Commit a placeholder row, reload the page → the placeholder survives (persisted via setDraft).
  • Title field validation: Foo # (double space) is rejected (basename ends in whitespace); Foo# (no space) is rejected; Foo # bar is rejected (mid-string #).
  • Wikitext preview modal on a placeholder row shows the placeholder verbatim plus a hint explaining the publish-time substitution.
  • npm run build passes (undef-ref scanner clean).

🤖 Generated with Claude Code

Merge request reports

Loading