Skip to content

Seed PocketCastsSharedModels with two cross-target DTOs#4255

Draft
mokagio wants to merge 3 commits into
trunkfrom
mokagio/shared-models-kernel
Draft

Seed PocketCastsSharedModels with two cross-target DTOs#4255
mokagio wants to merge 3 commits into
trunkfrom
mokagio/shared-models-kernel

Conversation

@mokagio

@mokagio mokagio commented May 5, 2026

Copy link
Copy Markdown
Contributor
📘 Part of: #

Fixes PCIOS-

Why

We have a handful of small Codable DTOs that are physically members of two or more Xcode targets via target membership in podcasts.xcodeproj (e.g. CommonUpNextItem is in podcasts, WidgetExtension, and PodcastsIntents).
That pattern compiles the same source three times, gives every target a private copy of the type, and makes a rename or signature change a coordination problem across Xcode targets — with the failure mode being silent runtime breakage at the App Group / process boundary the type was meant to cross.

This PR seeds a new SPM module, PocketCastsSharedModels, and moves two of those DTOs (CommonUpNextItem, SiriPodcastItem) into it.
The goal is not these specific files — it's to break ground on the pattern, so future shared types land in a real module instead of accreting more multi-target memberships.

The commit messages have the rationale per step; the PR is intentionally three small commits so it's easy to walk:

  1. Add Codable round-trip tests for shared DTOs — locks the wire format in the existing test target before the move.
  2. Extract CommonUpNextItem into shared module — creates the module, the test target, the test plan entry, and moves the first file.
  3. Move SiriPodcastItem into shared module — same playbook.

Intentional tradeoffs

  • The DTOs gain public initializers and public properties.
    No behaviour change, just access.
  • PodcastsIntentsUI had a stale target membership of SiriPodcastItem but no source actually referencing it.
    The membership is dropped without adding a SwiftPM dependency.
  • SharedConstants is a sensible follow-up but stays out of this PR.
    Moving it would touch seven targets and require pulling Share Extension into XcodeSupport.targets, which is meaningfully larger surgery than this kernel should carry.

Gotchas

  • UnitTests.xctestplan uses containerPath = container:Modules for the new test target.
    The other module test entries use decorative paths like container:Modules/Utils that don't resolve to real directories — Xcode tolerates them historically, but a fresh entry needs the actual package path or xcodebuild fails the build with "isn't a member of the specified test plan or scheme".
  • Touched podcasts.xcodeproj/project.pbxproj to drop file references, build files, group children, and target memberships for the moved files.
    Each removal is a single-line surgical delete; plutil -lint is clean.

How to test

  • make build_staging should succeed (verified locally on iPhone 17 / iOS 26.4 simulator, all extensions + watch + app clip + TV app).
  • make test_staging ONLY_TESTING=PocketCastsSharedModelsTests should pass 3 tests (2 CommonUpNextItem round-trips + 1 SiriPodcastItem round-trip).
  • make test_staging ONLY_TESTING=PocketCastsTests should still be green (323 passed, 0 failures, 1 expected failure locally).
  • Smoke check the widgets and Siri shortcut flows on device or simulator if you want extra confidence — those are the cross-process consumers.

Checklist

  • I have considered if this change warrants user-facing release notes and have added them to CHANGELOG.md if necessary. (no user-facing change)
  • I have considered adding unit tests for my changes.
  • I have updated (or requested that someone edit) the spreadsheet to reflect any new or changed analytics. (no analytics change)

Posted by Claude (Opus 4.7) on behalf of @mokagio with approval.

mokagio and others added 3 commits May 5, 2026 16:40
`CommonUpNextItem` and `SiriPodcastItem` are passed across process boundaries
(main app ↔ widget extension, main app ↔ intents extension) via App Group
storage and Siri shortcut payloads.
The Codable representation is the wire format, so a field rename or type
change would silently break the consumers at runtime.

Lock the contract with explicit round-trip tests now, before extracting the
types into a new shared SPM module.
The tests will move alongside the types in a follow-up commit.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Seeds a new `PocketCastsSharedModels` SPM module and moves
`CommonUpNextItem` into it as the kernel of cross-target sharing.

The DTO was previously a member of three Xcode targets via target
membership in `podcasts.xcodeproj` (`podcasts`, `WidgetExtension`,
`PodcastsIntents`).
That pattern compiles the same source three times and gives every target
a private copy of the type, which is brittle: a rename or signature
change must be coordinated across copies, and divergence is invisible
until something fails at the App Group / process boundary the type was
meant to cross.

Owning the type in a real module replaces three copies with one,
gives us a place to test the wire format, and proves the wiring for
follow-up moves.
`SiriPodcastItem` lands next in a separate commit using the same
pattern; `SharedConstants` is a future move that will pull
`Share Extension` into the SPM `XcodeSupport` list.

The module test target is registered in `UnitTests.xctestplan` so
`-only-testing:PocketCastsSharedModelsTests` runs under the
`Pocket Casts Staging` scheme alongside the other module tests.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same playbook as `CommonUpNextItem`: lift the DTO out of the
multi-target-membership pattern and into `PocketCastsSharedModels`.

The file was a member of `podcasts`, `PodcastsIntents`, and
`PodcastsIntentsUI` in `podcasts.xcodeproj`, but `PodcastsIntentsUI`
contains no source that actually references the type — that membership
was stale.
The new module is added as a SwiftPM dependency only on `podcasts` and
`PodcastsIntents`; `PodcastsIntentsUI` keeps the dependency list it
already had.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mokagio mokagio self-assigned this May 5, 2026
@dangermattic

Copy link
Copy Markdown
Collaborator
2 Warnings
⚠️ Modules/Package.swift was changed without updating its corresponding Package.resolved. Please resolve the Swift packages in Xcode.
⚠️ View files have been modified, but no screenshot or video is included in the pull request. Consider adding some for clarity.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

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