Squash of 63 commits spanning waves 46-93 (plus interspersed docs commits) of the gradual Postbox->TelegramEngine consumer-side migration. Scope: 139 files changed, 2123 insertions(+), 452 deletions(-). ## Themes by wave-block **Waves 46-58 — Peer field migrations + facade additions** Foundational EnginePeer convenience init additions (PeerReference, RenderedPeer, SelectivePrivacyPeer). Multiple `peer: Peer` field migrations across PeerInfo, ChatList, and SettingsUI components. **Waves 59-73 — peer field cascade + EnginePeer wrap drops** Series of single- to two-file peer-field migrations; consumer-side wrap removal (`EnginePeer(peer)` -> direct EnginePeer use); `as? TelegramUser` cast conversion to `case let .user(...)` enum match. Wave 64: RenderedPeer convenience init. Wave 68: SelectivePrivacyPeer convenience init. **Waves 74-83 — controller-Node bridge cleanup + small migrations** Wave-71 shadow-pattern cleanup at controller->Node bridges. Migrations of ChatRecentActionsController.peer (74), PeerInfoMember (75), MentionChatInputPanelItem (76), PassportUI SecureIdAuthController (77), AccountWithInfo + ShareController (78), peerInputActivitiesPromise (79), InactiveChannel (80), BlockedPeers (81), openHashtag resolveSignal (82), NotificationExceptionsList (83). **Waves 84-90 — TelegramEngine.Resources facade migrations** Per-method Shape-A/B sweeps converting `<ctx>.account.postbox.mediaBox.X(...)` to `<ctx>.engine.resources.X(...)`. Wave 90 was a single-commit big sweep: 40 fetchedMediaResource sites in 25 files migrated to engine.resources.fetch facade in one atomic pass with first-pass-clean build. Methods covered: storeResourceData, completedResourcePath, cancelInteractiveResourceFetch, resourceRangesStatus, resourceStatus, fetch (fetchedMediaResource). **Waves 91-92 — additional type migrations** Wave 91: ItemListWebsiteItem.peer + RecentSessionsController enum-case payload + openWebSession callback Peer? -> EnginePeer?. Wave 92: ChatListController StateHolder.EntryContext status type MediaResourceStatus -> EngineMediaResource.FetchStatus. **Wave 93 — speculative `import Postbox` drop sweep** Drop import from 7 wave-touched files where it became unused; restore in 5 files where bare PeerId/Message/MediaId/StoryId references escaped the pre-flight regex. Includes one MediaId(...) -> EngineMedia.Id(...) swap in InAppPurchaseManager to unlock its import drop. ## Build state Final state at squash: clean Telegram/Telegram build at debug_sim_arm64. ## Persistent-state notes - Pre-existing WIP unchanged across the squashed range: - build-system/bazel-rules/sourcekit-bazel-bsp submodule marker - Untracked: build-system/tulsi/, submodules/TgVoip/, third-party/libx264/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.3 KiB
Wave 48 — PeerInfoScreenData.savedMessagesPeer Peer? → EnginePeer?
Date: 2026-04-25
Predecessor: Wave 47 (commit d7b7536440) — stored PHN.peer single-file private migration.
Shape: Cross-file struct-field migration. Storage class is internal to PeerInfoScreen module; no external consumer references PSD.savedMessagesPeer.
Target
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift, PeerInfoScreenData.savedMessagesPeer: Peer? at line 388.
Pre-flight inventory
grep -rEn "(\w+\??)\.savedMessagesPeer\b" submodules/ Telegram/ → matches only inside submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/. No external consumer. The same field name appears in unrelated places (TelegramEngineMessages.swift, ChatListUI, etc.) but those are different declarations on different types.
Within PeerInfoScreen module:
| Site | Code | Action |
|---|---|---|
PeerInfoData.swift:388 |
let savedMessagesPeer: Peer? (struct field decl) |
Type change → EnginePeer? |
PeerInfoData.swift:444 |
savedMessagesPeer: Peer?, (init param) |
Type change → EnginePeer? |
PeerInfoData.swift:489 |
self.savedMessagesPeer = savedMessagesPeer (assignment) |
No change (passthrough) |
PeerInfoData.swift:1029 |
savedMessagesPeer: nil, (init kwarg) |
No change (nil works for either) |
PeerInfoData.swift:1102 |
savedMessagesPeer: nil, |
No change |
PeerInfoData.swift:1313–1317 |
let savedMessagesPeer: Signal<EnginePeer?, NoError> (local) |
No change — already EnginePeer? |
PeerInfoData.swift:1622 |
savedMessagesPeer: savedMessagesPeer?._asPeer(), |
Drop bridge → savedMessagesPeer: savedMessagesPeer, |
PeerInfoData.swift:1869 |
savedMessagesPeer: nil, |
No change |
PeerInfoData.swift:2207 |
savedMessagesPeer: nil, |
No change |
PeerInfoScreen.swift:5399 |
peer: self.data?.savedMessagesPeer.flatMap(EnginePeer.init) ?? self.data?.peer, |
Drop bridge → peer: self.data?.savedMessagesPeer ?? self.data?.peer, |
PeerInfoScreen.swift:5805 |
same as :5399 | Same drop |
Total edits: 5 (3 in PID, 2 in PIS).
EnginePeer / read-site audit
The local signal at PeerInfoData.swift:1313 already produces EnginePeer? from engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(...)). The ?._asPeer() at line 1622 was an artificial demotion. Migrating the field type to EnginePeer? removes both the demotion at the storage site and the flatMap(EnginePeer.init) re-promotions at the read sites — a clean ratchet.
PIS:5399 and :5805 use the field as input to headerNode.update(... peer: ...), whose peer parameter has been EnginePeer? since wave 45. The ?? coalescing operand is self.data?.peer (already EnginePeer?). Result: drop the .flatMap(EnginePeer.init) and the expression compiles.
Edit list
PeerInfoData.swift (3 edits)
- Line 388:
let savedMessagesPeer: Peer?→let savedMessagesPeer: EnginePeer? - Line 444:
savedMessagesPeer: Peer?,→savedMessagesPeer: EnginePeer?, - Line 1622:
savedMessagesPeer: savedMessagesPeer?._asPeer(),→savedMessagesPeer: savedMessagesPeer,
PeerInfoScreen.swift (2 edits, identical text)
- Line 5399:
peer: self.data?.savedMessagesPeer.flatMap(EnginePeer.init) ?? self.data?.peer,→peer: self.data?.savedMessagesPeer ?? self.data?.peer, - Line 5805: same
Use replace_all=true for the PIS edit since the matched text appears at both call sites verbatim.
Out of scope
PeerInfoScreenData.chatPeer— large blast radius (5as? TelegramXchecks downstream + ClearPeerHistory init parameter), defer.PeerInfoScreenData.linkedDiscussionPeer,linkedMonoforumPeer— both haveas? TelegramChannelconsumer sites inPeerInfoProfileItems.swift. Defer.PeerInfoScreenMemberItem.enclosingPeer— defer (separate target).
Build & verify
Same Bazel command as wave 47. Expected 1-iteration first-pass-clean (single-pattern bridge removal, no enum-case rewrites, no Peer-only property access).
Commit
Postbox -> TelegramEngine wave 48. Body lists the 5-edit summary and notes −3 internal bridges (1 PID + 2 PIS, identical PIS text appears twice).
Outcome capture
Append a Wave 48 entry to docs/superpowers/postbox-refactor-log.md and update memory file project_postbox_refactor_next_wave.md.