**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 ==
1. Drop a file with original name `IMG_1234.jpg` → Title cell pre-fills to `IMG_1234`.
2. Click into the Title cell, replace with "Sunset over the bay", press Enter.
3. 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.