Skip to content

Persistent alias state in .pad frontmatter to skip recovery card on copies #48

@Jaggob

Description

@Jaggob

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:

  1. Lookup the target via BindingService::findByPadId(padId, STATE_ACTIVE).
  2. Authorisation round-trip via UserNodeResolver — identical semantics to the existing findOriginalForCopy.
  3. 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).
  4. 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:

  1. 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.
  2. 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)

  • alias_of_pad_id recognised in .pad frontmatter by PadFileService
  • PadOpenService::openNode resolves alias before falling back to missing-binding
  • Authorisation via UserNodeResolver on the target file every time
  • Endpoint to write the alias marker, gated by an "Open the original" recovery click
  • Recovery card surfaces the opt-in (checkbox or dedicated button)
  • Decision documented on whether alias snapshots stay frozen or get mirrored from the original
  • Manual smoke: copy a .pad, set alias, reopen → opens original directly; delete original → reopen → recovery card returns

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions