# 29-Apr-2026 — rebuild draft leader on copy and backfill stale walk_leader [closes #251](https://github.com/nbarrett/ngx-ramblers/issues/251)

## [build 629](https://github.com/nbarrett/ngx-ramblers/actions/runs/25131677331) — [commit 59d78a4](https://github.com/nbarrett/ngx-ramblers/commit/59d78a43fa43e3c78e56d4d85477115e73dc0ecc)

_____

Two related fixes for one root cause: the legacy walks schema's flat
contactId field was fanned out at migration time into four new-schema
fields (groupEvent.walk_leader.id/name, fields.contactDetails.contactId,
fields.publishing.ramblers.contactName), and the current edit UI only
keeps three of those four in sync when a leader is selected. Any stale
override on the legacy contactId therefore stayed embedded in
groupEvent.walk_leader indefinitely, and the copy-from-previous-walk
flow then propagated it onto every walk copied from such a record.

Reported by Kerry O'Grady after Jenny Brown's Bearsted Circular walk
copied on 2026-04-19 came through with Nick Barrett as the Ramblers
contact. Tracing the affected 2026 walk and its 2024 source confirmed
the chain: a 2024 admin override of contactId, faithfully migrated, then
faithfully spread-merged forward in 2026.

Code fix - projects/ngx-ramblers/src/app/pages/walks/walk-edit/walk-edit-copy-from.ts
---
copyDetailsFromPreviousWalk() previously did
{ ...targetFields, ...sourceFields }
{ ...targetGroupEvent, ...sourceGroupEvent }
which let the source walk's leader-shaped fields win the merge.

Now, after the merge, rebuildLeaderIdentity re-derives every leader
field from the draft's own pre-merge contactDetails.memberId via
eventDefaultsService.memberToContact:

- fields.contactDetails (memberId, contactId, displayName, email, phone)
- fields.publishing.ramblers.contactName (member.contactId)
- fields.publishing.meetup.contactName (null; meetup has its own flow)
- groupEvent.walk_leader (Contact built from the current Member)

The publish booleans on both channels are preserved so the copier's
on/off choices survive. groupEvent.status is also deleted on the clone
so the source walk's "confirmed" status does not silently promote the
new draft past its approval gate.

The previous deletes of fields.contactDetails, fields.publishing, and
groupEvent.walk_leader on the clone are no longer needed - the rebuild
is now authoritative for all four leader fields, anchored on the
draft's pre-merge memberId.

Data backfill - server/lib/mongo/migrations/database/20260424000000-backfill-walk-leader-from-member.ts
---
The code fix prevents new leakage but does not heal walks already in
the database with stale groupEvent.walk_leader from the migration-time
fan-out. Idempotent backfill:

- Scope: fields.inputSource = "manually-created" with a non-null
fields.contactDetails.memberId. Ramblers-cached and file-import walks
are deliberately out of scope - their walk_leader.id is a Ramblers
Walks Manager contactId, not a Member _id, and rewriting it would
break the Ramblers submission path.
- For each walk, look up the Member by memberId and compute the
expected walk_leader using the same shape memberToContact produces
on new walks (is_overridden, id=member._id.toString(), name,
telephone, has_email).
- Compare current walk_leader to expected field-by-field. Equal: skip.
Missing or different: replace.
- Walks whose memberId no longer resolves to a Member are logged and
left inert rather than silently cleared.

Re-runs find every doc already matching and perform no writes, so the
migration is safe to replay during rollout or after partial runs.
fields.contactDetails.contactId and fields.publishing.ramblers.contactName
are intentionally not touched: the leader-toggle UI in walk-edit-leader.ts
lets users diverge them from the member record on purpose, and the
backfill should not undo that.

Runs automatically on deploy alongside the rest of the schema migrations
- not registered under manualMigrations in migrations-config.ts. The
backfill is idempotent and the scope filter (manually-created walks
with a memberId) keeps it cheap on environments that have no affected
documents, so there is no benefit to gating it behind an explicit
admin trigger.