Scope. Let the user fill the Caption column from the Title column via a {title} variable in the per-column default value, with a one-click "link" affordance between the two headers when they sit next to each other.
What it solves. "Many times the caption is the title (or the title plus a few words)." Today filling a caption per row is fully manual; the user wants a column-level recipe like Photo of {title} that resolves to the right caption per file.
What's wanted
- {title} variable in caption defaults. The Caption column's per-column default value (already settable from the column header chevron menu — see HeaderMenuPopover in src/table.jsx:4047) gains support for a {title} token. When the default is applied to a row (via the existing "Apply to blank cells / Apply to all selected / Overwrite selected / Overwrite all" split-button at src/table.jsx:4280-4354), {title} is replaced by that row's title.
- Free-form prefix and suffix. No separate "prefix" / "suffix" inputs — they're just the literal text the user types around the token. e.g. default = Photo of {title} — taken by me → row with title Sunset over Lake resolves to Photo of Sunset over Lake — taken by me. The whole default field is a template string.
- Strip the auto-numbering placeholder from {title}. The auto-sequence task (T425984) introduces a trailing # that gets resolved to a concrete integer at publish time. The {title} substitution must NOT include that #. Strip a trailing \s*#\s*$ before substitution (regardless of whether T425984 has shipped — the convention is forward-compatible). Same applies to whatever final form T425984 lands on; if the placeholder character changes, this code stays the single point of stripping.
- Inter-header link icon (the "linking icon" the user described). When Title (title) and Caption (description) sit adjacent in the visible column order (in either direction — Title→Caption or Caption→Title), render a small chain icon centered on the boundary between their two header cells. Clicking it:
- Opens the Caption column's HeaderMenuPopover (existing chevron menu).
- Pre-expands the "Set default value" panel.
- If the current default is empty, pre-fills it with {title}. If non-empty, leaves it alone (don't clobber a user's existing string) but still opens the panel so the user sees the field and can edit.
- Tooltip: "Link Caption ← Title".
- Visible only when adjacent — when the columns aren't neighbors, the icon doesn't render at all.
- Caption header dropdown is the always-available fallback. The Caption column header chevron already opens HeaderMenuPopover. Add a dedicated menu item — "Insert {title} variable" — above (or alongside) "Set default value", that does the same pre-fill as the link-icon click. This is the path users take when the columns aren't adjacent (e.g. Caption is far to the right with other columns in between).
Where the code lives
- Header rendering / column adjacency check. src/table.jsx:786 — the visibleColumns.map((c, ci) => <HeaderCell ...>) loop. Adjacency = check visibleColumns[ci] and visibleColumns[ci+1] against the { "title", "description" } pair.
- Header link icon visual. New element rendered between two adjacent HeaderCells. Two practical placements:
- Inside the right-hand HeaderCell, anchored at its left edge (negative margin).
- As a sibling absolutely-positioned element overlaying the column boundary. Either approach is fine; the icon itself is Icon name="link" (already in src/icons.jsx:42). 14-16 px, opacity ~0.55 idle, 1.0 on hover, with a tooltip via title="...". Click opens Caption's HeaderMenuPopover (programmatically set headerMenu state in Table to { colKey: 'description', anchorEl: <icon ref> }).
- Default-value editor for description. src/table.jsx:4233-4242 — currently the default branch (just an <input>). Two tweaks:
- Add a "Insert {title}" chip/button next to the input that appends {title} at the cursor (or at the end if focus isn't in the input). Mirrors the existing "Me" / "{{own}}" quick-insert pattern at src/table.jsx:4189-4196 (author) and src/table.jsx:4223-4231 (source).
- Below the input, a small live-preview line: Preview: <expanded value from a sample row>. Helps the user see what their template will produce.
- Token expansion at apply-time. Currently applyDefault / overwriteWithDefault in Table (src/table.jsx:912-935) write def into it[k] directly. For Caption, route through a new helper expandCaptionTemplate(template, item) → string that:
- Returns template unchanged if template doesn't contain {title}.
- Otherwise replaces {title} with stripSequencePlaceholder(item.title) where stripSequencePlaceholder removes a trailing \s*#\s*$.
- Returns the resulting string (may be empty if template was just {title} and the row has no title — that's fine, "apply to blanks" already only fills when the destination is blank, so this is a no-op for blank-blank rows).
- Header menu item insertion. src/table.jsx:4251-4432 (<ul className="hdr-pop__menu">). Add a new <li> for "Insert {title} variable" gated to col.key === 'description'. Clicking it: expands the default-value panel and appends {title} to value. (Reuse the same onChange already wired to setColumnDefaults.)
- No new persistence layer. The default value lives in columnDefaults['description'] which is already persisted via setPref('columnDefaults', ...) (see src/api/user-store.js:770). Just stays a string; the {title} token is data, not metadata.
UX details
- Per-column default of {title} for Caption is treated as "no default" by hasDefault indicator dot in the header (src/table.jsx:1288)? No — keep treating it as having a default (the dot is a generic "this column has a default" marker; users will quickly learn that the dot for Caption means "templated").
- Clicking the link icon when a default already exists: open the popover, expand the panel, scroll the input into view — no overwrite. If the existing default doesn't contain {title}, the popover surfaces the "Insert {title}" chip so the user can append it.
- The token {title} is the only one in v1. If we later want {filename}, {author}, etc., the same expansion helper grows; out of scope here.
- Case-sensitive match for {title} — keep it simple. We can normalize or accept {Title} later if needed.
Acceptance
- Caption header chevron → "Set default value" → typing Photo of {title} and clicking "Apply to blank cells" fills every blank caption with Photo of <that-row's-title>.
- A row whose title ends with the T425984 sequence placeholder (e.g. Sunset over Lake #) resolves the variable to Sunset over Lake (no trailing #, no trailing whitespace).
- A row whose title is empty resolves the variable to the empty string; the rest of the template still renders.
- When Caption and Title are adjacent (regardless of direction), a chain icon appears between the two header cells; clicking it opens the Caption header menu with the default-value panel expanded.
- When Caption and Title are NOT adjacent, the icon does not appear; the "Insert {title} variable" item in the Caption header dropdown is the access path.
- The default-value editor for Caption shows a one-click "Insert {title}" chip and a small live preview of the expanded result for a sample row.
- {title} substitution is one-shot at apply time — editing a title later does NOT retroactively update existing captions (consistent with the existing "default value + apply scope" model). The user re-clicks "Apply / Overwrite" if they want a refresh.
- No regressions to other columns' default-value editors or to the Caption column's existing per-row editing.
Out of scope
- Live two-way binding (caption auto-updates when title changes after the first apply). The existing model is one-shot fill; this task stays consistent with it.
- Other variables besides {title} (e.g. {filename}, {author}, {date}). Easy to grow later — keep the expansion helper extensible but only {title} is wired now.
- Reverse direction ({caption} substituting into Title). The user explicitly asked Caption ← Title.
- Per-row override: if the user clicks "Apply to blank cells" with a {title} template and one row has a custom non-blank caption, that row stays untouched (existing apply-to-blanks semantics).
Source: User feedback session (T426424).
Related: T425984 (auto-sequence # placeholder — informs the strip-rule for {title} expansion), and the existing column-header menu plumbing at src/table.jsx:4029 (HeaderMenuPopover).