Skip to content

fix(share): include referenced choreographers in program share bundle so received dances keep author attribution (#412)#418

Merged
ibanner56 merged 3 commits into
mainfrom
isaacbanner-fix-share-bundle-choreographers
Jul 20, 2026
Merged

fix(share): include referenced choreographers in program share bundle so received dances keep author attribution (#412)#418
ibanner56 merged 3 commits into
mainfrom
isaacbanner-fix-share-bundle-choreographers

Conversation

@ibanner56

Copy link
Copy Markdown
Owner

The bug (#412)

Sharing a program via the OS share sheet (AirDrop, etc.) produced a .ccshare bundle whose dances kept their authorIds but whose archive omitted the referenced choreographer records (buildProgramShareBundle left choreographers empty). User-visible symptom: a received shared dance imported with no choreographer / author attribution.

This is distinct from the already-fixed #410 (slots not resolving to dances): here the dance resolved fine but showed no choreographer.

Root cause — two sides

Send side omitted the choreographers entirely.

Receive side could not have honored them anyway: GenericJsonAdapter re-serialized each dance into a single-dance archive without choreographers, so parse produced a draft with the sender's raw authorIds but empty authorNames. ImportPipeline.commit then kept those raw ids, which reference choreographer rows the receiver never creates — failing the dance_authors foreign key (PRAGMA foreign_keys = ON) and dropping the dance. This was a pre-existing, production-affecting bug that also broke the manual "Caller's Compendium JSON" import of any authored dance onto a device lacking those exact ids.

Both fixes are required for the program-share round-trip, so they ship together in this one atomic PR.

The fix

Send side (app)buildProgramShareBundle collects the DISTINCT choreographers referenced by the bundled dances' authorIds (deduped by id, stable first-seen order, unresolved id skipped / never fatal) via a new injected choreographerFor lookup, and embeds only those. Each is sanitized first — email/location cleared per the Choreographer privacy contract — so the shared file leaks no private contact data (name/website/notes/deceased preserved). Threaded through ProgramExportMenu and both screen call sites; a choreographersById map was added to CollectionData.

Receive side (core)GenericJsonAdapter now captures the archive's choreographers during discover, embeds the ones a dance references into its self-contained single-dance payload, and populates StructuredDraft.authorNames (in authorIds order, blanks/unresolved dropped). The existing pipeline name-resolution matches or creates the choreographer row and rewrites authorIds to the receiver's own ids — no FK failure. commit's author logic is untouched. Full-DB restore uses a separate path (archive_service upserts choreographers directly) and is unaffected.

End-to-end verification

A round-trip test builds a bundle from an authored dance, then imports it on a fresh CompendiumArchiveImporter over the real, FK-enforced database: the dance imports, the program slot resolves (no placeholder), the resolved choreographer name matches the sender, and email/location are null.

OWASP / untrusted input

The received archive stays untrusted — no ArchiveIntakeService validation relaxed (size cap, UTF-8, well-formed-archive, forward-schema refusal, never-throw). Choreographer creation now driven by the bundle flows through the same validated decode + parameterized-drift path the manual import already crosses (no new sink): names are data-only, blank/whitespace names dropped, rows bounded by dances×authors under the existing 25 MiB intake cap and deduped by normalized name.

Tests

  • Send side: referenced-only, dedupe-once, no-authors, unresolved-id skipped, email/location stripped.
  • Adapter-level class fix: author-name recovery from payload choreographers; commit creates/points-at the receiver's row with no FK error; reuse-by-name (no duplicate).
  • End-to-end program-share round-trip: on the real FK-enforced DB — dance imports, slot resolves, author name matches, email/location null.

Gates

  • fvm dart format . — clean
  • fvm flutter analyze (core + app) — 0 issues
  • core fvm dart test1798 passed, 4 skipped (pre-existing)
  • app fvm flutter test1642 passed

Refs #412. Share lineage #298.

… so received dances keep author attribution (#412)

When a program is shared via the OS share sheet (AirDrop, etc.),
`buildProgramShareBundle` built a CompendiumArchive with the referenced
dances but left `choreographers` empty. Bundled dances carried `authorIds`
pointing at choreographer records the archive omitted, so on the receiving
device a shared dance imported with no choreographer/author attribution.

Two coordinated fixes, both required for the round-trip:

Send side (app): buildProgramShareBundle now collects the DISTINCT
choreographers referenced by the bundled dances' authorIds (deduped by id,
stable first-seen order, unresolved id skipped/never fatal) via a new
injected `choreographerFor` lookup, and embeds only those. Each is
SANITIZED first — email/location are cleared per the Choreographer privacy
contract (choreographer.dart:6-9) — so the shared file leaks no private
contact data. Threaded through ProgramExportMenu and both screen call sites
(a `choreographersById` map was added to CollectionData).

Receive side (core): GenericJsonAdapter previously re-serialized each dance
into a single-dance archive WITHOUT choreographers, so parse produced a draft
with the sender's raw authorIds but empty authorNames; commit then kept those
raw ids, which reference choreographer rows the receiver never creates —
failing the dance_authors foreign key (PRAGMA foreign_keys = ON) and dropping
the dance (a pre-existing, production-affecting bug that also broke manual
"Caller's Compendium JSON" import of any authored dance). The adapter now
captures the archive's choreographers, embeds the ones a dance references in
its self-contained payload, and populates StructuredDraft.authorNames (in
authorIds order, blanks/unresolved dropped). The existing pipeline
name-resolution then matches or creates the choreographer row and rewrites
authorIds to the receiver's own ids — no FK failure. commit's author logic
is untouched. Full-DB restore uses a separate path and is unaffected.

OWASP: the received archive stays untrusted — no ArchiveIntakeService
validation relaxed. Choreographer creation is driven only through the same
validated decode + parameterized-drift path the manual import already uses;
blank/whitespace names are dropped, rows are bounded by dances x authors
under the existing 25 MiB intake cap and deduped by normalized name.

Tests: send-side (referenced-only, dedupe-once, no-authors, unresolved-id
skipped, email/location stripped), adapter-level class fix (name recovery,
commit creates/points-at receiver row with no FK error, reuse-by-name), and
full end-to-end program-share round-trip on a fresh CompendiumArchiveImporter
over the real FK-enforced database (dance imports, slot resolves, author name
matches, email/location null).

Refs #412. Share lineage #298.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 20, 2026 23:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes program-share/import author attribution by ensuring referenced choreographers are included in the shared archive and that the receive-side JSON adapter carries those choreographer names through to the import pipeline so author IDs can be resolved/created safely.

Changes:

  • App share-bundle builder now embeds only the choreographers referenced by bundled dances (with private contact fields stripped).
  • Core GenericJsonAdapter now preserves referenced choreographers in single-dance payloads and populates StructuredDraft.authorNames from payload choreographers.
  • Adds adapter-level, send-side, and end-to-end share/receive tests (including FK-enforced DB round-trip).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/compendium_core/test/imports/generic_json_adapter_test.dart Adds regression tests ensuring author names are recovered and commits create/reuse choreographer rows without FK failures.
packages/compendium_core/lib/src/imports/generic_json_adapter.dart Captures archive choreographers during discover, embeds referenced choreographers in fetch payloads, and derives authorNames during parse.
app/test/program_share_bundle_test.dart Expands tests to cover choreographer inclusion, privacy stripping, and full end-to-end receiver attribution behavior.
app/lib/src/widgets/program_export_menu.dart Threads a choreographerFor resolver into the “Share (program + dances)” action.
app/lib/src/search/collection_data.dart Adds a choreographersById map to support share/export author resolution.
app/lib/src/screens/program_summary_screen.dart Supplies choreographerFor from loaded collection data to the export menu.
app/lib/src/screens/program_editor_screen.dart Supplies choreographerFor from loaded collection data to the export menu.
app/lib/src/export/program_share_bundle.dart Builds share bundles that include only referenced choreographers and sanitizes private fields before encoding.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/lib/src/export/program_share_bundle.dart Outdated
ibanner56 and others added 2 commits July 20, 2026 16:09
…ents (#412)

encodeArchive canonicalizes entities by id, so the emitted bundle's
choreographer order is by id, not reference/first-seen order. Only the SET of
included choreographers is significant (parse recovers author names in the
dance's authorIds order directly from the decoded dance). Doc-only; no behavior
change. Addresses Copilot review feedback on #418.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
#412)

The send-side redaction was an inline copyWith(clearEmail, clearLocation) in
buildProgramShareBundle. The Choreographer privacy contract
(choreographer.dart:6-9) covers all program/dance share paths, and the
forthcoming dance-share path (#298) must apply the identical redaction — an
inline copyWith is exactly the kind of guarantee that gets silently forgotten
when that path is added.

Extract it into a top-level, reusable `sanitizeChoreographerForShare` in a new
export/share_sanitization.dart, doc-commented against the privacy contract
(clears email+location; preserves name/website/notes/deceased/identity).
buildProgramShareBundle now calls it. Add a direct unit test pinning the
guarantee independently of the bundle path (email+location cleared;
public fields preserved; no-op when no contact data).

No behavior change to the bundle. Addresses tech-lead review on #418.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@ibanner56
ibanner56 merged commit af7af73 into main Jul 20, 2026
7 checks passed
@ibanner56
ibanner56 deleted the isaacbanner-fix-share-bundle-choreographers branch July 20, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants