Scope: Add a "Restore from original filename" action to the Title column's header dropdown so users can revert per-row title edits back to the auto-default.
Why
When a stash file lands, its Title cell is pre-filled with the original filename minus the extension (T425880 — see src/api/normalize.js:211, src/api/normalize.js:553, src/ui/dropzone.jsx:45). The user then refines this into a descriptive Commons title. If they fat-finger the cell, accept a wrong autocomplete, or delete the title outright, the only way back to the original filename is to re-type it from item.filename — which is hidden by default in the table layout v10 (src/table.jsx:152-154). That's an unnecessary papercut for a recoverable mistake.
What
Add a new menu item under the Title column header dropdown (the chevron-triggered popover, HeaderMenuPopover in src/table.jsx:4047):
- "Restore from original filename" — sets title = filename.replace(/\.[^.]+$/, '') for the targeted rows, mirroring the auto-default formula. The published filename gets the extension reattached at publish time via buildFutureFilename (src/api/title-validation.js), so no extension handling is needed in the restored value itself.
The action sits next to the existing "Set default value" / "Toggle required" / "Clear all values" entries and follows the existing split-button apply-scope pattern so the UX vocabulary stays consistent:
- Primary button: "Apply to blank titles" — fills only rows where title is empty (the most common, safest case).
- Caret menu: "Apply to selected" (selection-blank), "Overwrite selected" (selection-all), "Overwrite all" (window.confirm gated, like the existing Overwrite-all in the default-value split-button).
The action is rendered only when col.key === "title" (other columns don't have a per-row "original filename" to restore from).
Per-row writes go through the existing onUpdate plumbing (src/app.jsx:1021) so drafts persist to the user-store and the title-uniqueness check fires automatically. Published items (status === 'published') are skipped in the iteration so we don't briefly paint a non-persistent change onto the published-files view.
Files likely to change
- src/table.jsx — HeaderMenuPopover (~4047): add the conditional menu item + split-button. The four apply-scope handlers can be wired in the existing headerMenu && IIFE block (~891-1005) where applyDefault / overwriteWithDefault already live; the title-restore variant computes the value per-row from it.filename instead of the global columnDefaults[k].
- CHANGELOG.md — [Unreleased] line under Added.
No new exports / window-globals (everything stays inside table.jsx's closure). No persistence-model changes.
Repro of current pain
- Drop a file with original name IMG_1234.jpg → Title cell pre-fills to IMG_1234.
- Click into the Title cell, replace with "Sunset over the bay", press Enter.
- Realize you wanted the original after all → no built-in way to recover except retype.
Acceptance
- Title column header chevron menu shows a new "Restore from original filename" item.
- Item is hidden / not rendered for non-Title columns.
- "Apply to blank titles" only restores rows whose title is currently empty/whitespace.
- Selected and Overwrite-selected scopes are disabled when no rows are selected (matches existing pattern).
- "Overwrite all" confirms via window.confirm before running.
- Restored value is filename.replace(/\.[^.]+$/, '') (extension stripped — extension is reattached at publish via buildFutureFilename).
- Restoring triggers the existing draft-persist path in onUpdate (verified by reload — restored title sticks).
- Published rows are not mutated.
- npm run build passes (including the check-undefined-refs.mjs scanner).
- Manual exercise on the MR preview: restore-blank, restore-selected, overwrite-all all do what they say.