Follow-up to #45 / PR #47.
After #47 landed, opening a copied .pad shows the recovery card with "Open the original .pad file" every single time. If the user keeps the copy as a permanent shortcut, that's repetitive — they click the same button on every open, even though the desired outcome is always the same.
Proposal
Persist the user's preference in the .pad file itself, not in the DB. A new optional frontmatter field marks the copy as a permanent alias to the original pad. On open, the alias short-circuits the recovery flow and loads the original directly.
---
pad_id: copy-pad-id-here
access_mode: protected
alias_of_pad_id: original-pad-id-here # new optional field
---
(alias_of_pad_id is preferred over alias_of_file_id because it's stable across rename / move of the original, and reuses the findByPadId lookup we already added for find-original.)
Open flow
PadOpenService::openNode already does a findByFileId lookup before anything else. Extend it: when no binding row exists and the frontmatter carries an alias_of_pad_id field:
- Lookup the target via
BindingService::findByPadId(padId, STATE_ACTIVE).
- Authorisation round-trip via
UserNodeResolver — identical semantics to the existing findOriginalForCopy.
- If the target is readable → respond with the open payload of the original pad (same shape as if the user had directly opened the original
.pad file).
- If the target is gone or unreadable → fall through to the regular
missing_binding path, so the user gets the recovery card again and can re-decide.
The viewer / embed never sees that aliasing happened — they get a normal open payload. Clean layering.
Trigger / UX
In the recovery card, when "Open the original .pad file" is the primary action, add either:
- a small checkbox/toggle "Always open the original from this file" next to the button — checked state writes the alias marker to the copy's frontmatter before redirecting, OR
- a separate button "Open original (and remember)" that does the same thing in one click.
Either way the alias is opt-in, not implicit. The plain "Open the original" button stays available for one-off cases.
Authorisation
Same non-negotiable property as #45: when the alias is followed, the open flow must verify the requester can already read the target file (UserNodeResolver round-trip). If they can't, the alias path silently falls back to the recovery card — no information leak about who else owns a pad with that pad_id.
Trade-off worth deciding before implementation
The aliased copy gets no further snapshots. Sync writes only land in the original's .pad file (the one the binding belongs to). The alias copy keeps the snapshot frontmatter from the moment it was created, frozen forever.
That has two consequences:
- If the original
.pad is deleted (and with it the pad on Etherpad), the alias copy is left with a stale snapshot from the moment of the copy — possibly months old — and no live pad behind it. The recovery card will surface again with "no matching pad", but the "Create new pad from this file" action will fork a long-outdated version of the content. The user might not realise how stale the alias's snapshot is.
- A WebDAV download of the alias copy returns the stale snapshot, not the current pad state. A user who treats the alias as a regular
.pad for backup purposes is getting an out-of-date copy.
Two reasonable resolutions:
- Accept the trade-off: the alias is documented as "lives and dies with the original". If the original is deleted, the alias is just a stale snapshot, and the user is responsible for cleaning it up. Simpler implementation, no extra sync.
- Mirror the snapshot: the sync job, when it writes the original's
.pad, also writes the same snapshot into every alias pointing at it. Keeps both files current, but introduces an N-way fanout that costs writes and complicates the sync contract. Probably overkill unless aliases become common.
Worth deciding which of these we want before building this feature — the rest of the design follows from that choice.
Alternatives that don't need a new feature at all
- Status quo: the recovery card shows every time. One extra click per open. Lowest cost.
- Per-browser localStorage dismiss: "don't show this again for fileId X" lives in the browser, not in the file. Cheap, but per-device and lost on cache clear.
- Rewrite copy into a pure pointer file: clicking "Open original" strips the copy's own snapshot from its frontmatter and leaves only an alias marker. Forces the lives-and-dies-with-the-original outcome but destroys the snapshot the user might still have wanted as a fork seed.
Cost estimate
If we go with the "accept the trade-off" variant: ~150 LOC backend (alias detection in openNode + frontmatter helpers + endpoint to set the marker) + ~30 LOC frontend (checkbox + write call in the recovery card).
Acceptance (if pursued)
Follow-up to #45 / PR #47.
After #47 landed, opening a copied
.padshows the recovery card with "Open the original .pad file" every single time. If the user keeps the copy as a permanent shortcut, that's repetitive — they click the same button on every open, even though the desired outcome is always the same.Proposal
Persist the user's preference in the
.padfile itself, not in the DB. A new optional frontmatter field marks the copy as a permanent alias to the original pad. On open, the alias short-circuits the recovery flow and loads the original directly.(
alias_of_pad_idis preferred overalias_of_file_idbecause it's stable across rename / move of the original, and reuses thefindByPadIdlookup we already added for find-original.)Open flow
PadOpenService::openNodealready does afindByFileIdlookup before anything else. Extend it: when no binding row exists and the frontmatter carries analias_of_pad_idfield:BindingService::findByPadId(padId, STATE_ACTIVE).UserNodeResolver— identical semantics to the existingfindOriginalForCopy..padfile).missing_bindingpath, so the user gets the recovery card again and can re-decide.The viewer / embed never sees that aliasing happened — they get a normal open payload. Clean layering.
Trigger / UX
In the recovery card, when "Open the original .pad file" is the primary action, add either:
Either way the alias is opt-in, not implicit. The plain "Open the original" button stays available for one-off cases.
Authorisation
Same non-negotiable property as #45: when the alias is followed, the open flow must verify the requester can already read the target file (
UserNodeResolverround-trip). If they can't, the alias path silently falls back to the recovery card — no information leak about who else owns a pad with thatpad_id.Trade-off worth deciding before implementation
The aliased copy gets no further snapshots. Sync writes only land in the original's
.padfile (the one the binding belongs to). The alias copy keeps the snapshot frontmatter from the moment it was created, frozen forever.That has two consequences:
.padis deleted (and with it the pad on Etherpad), the alias copy is left with a stale snapshot from the moment of the copy — possibly months old — and no live pad behind it. The recovery card will surface again with "no matching pad", but the "Create new pad from this file" action will fork a long-outdated version of the content. The user might not realise how stale the alias's snapshot is..padfor backup purposes is getting an out-of-date copy.Two reasonable resolutions:
.pad, also writes the same snapshot into every alias pointing at it. Keeps both files current, but introduces an N-way fanout that costs writes and complicates the sync contract. Probably overkill unless aliases become common.Worth deciding which of these we want before building this feature — the rest of the design follows from that choice.
Alternatives that don't need a new feature at all
Cost estimate
If we go with the "accept the trade-off" variant: ~150 LOC backend (alias detection in
openNode+ frontmatter helpers + endpoint to set the marker) + ~30 LOC frontend (checkbox + write call in the recovery card).Acceptance (if pursued)
alias_of_pad_idrecognised in.padfrontmatter byPadFileServicePadOpenService::openNoderesolves alias before falling back to missing-bindingUserNodeResolveron the target file every time.pad, set alias, reopen → opens original directly; delete original → reopen → recovery card returns