Squashes 20 commits — the implementation and outcome commits of
waves 37 through 43 plus wave 44's spec and implementation-plan
docs — into a single commit. Per-wave lessons remain recorded in
docs/superpowers/postbox-refactor-log.md. The unrelated "Add swift
svg" commit is preserved separately outside this squash.
Wave 37 — peerTokenTitle: peer Peer → EnginePeer (1 file)
Wave 38 — canSendMessagesToPeer: peer Peer → EnginePeer (12 files)
Wave 39 — AccountContext.makePeerInfoController: peer Peer → EnginePeer (52 files)
Wave 40 — makeChatQrCodeScreen + makeChatRecentActionsController bundle (8 files)
Wave 41 — RenderedChannelParticipant.peer: Peer → EnginePeer (28 files)
Wave 42 — PeerInfoScreenData.peer: Peer? → EnginePeer? (17 files)
Wave 43 — PeerInfoScreen 6 helpers: peer Peer? → EnginePeer? (12 files)
Wave 44 — RenderedChannelParticipant.peers design doc + implementation plan
(impl and outcome land in subsequent commits, not part of squash)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 KiB
Wave 39 — makePeerInfoController peer: Peer → EnginePeer migration
Date: 2026-04-24
Context
Ring-2 cleanup of the AccountContext Peer-typed-API surface. Waves 34 (FoundPeer.peer), 35 (SendAsPeer.peer), 36 (ContactListPeer.peer), 37 (peerTokenTitle), and 38 (canSendMessagesToPeer) migrated adjacent Peer-typed APIs to EnginePeer. makePeerInfoController is the largest remaining Peer-typed-API surface on AccountContext and a natural follow-up.
Scope: only makePeerInfoController this wave. The sibling methods makeChatQrCodeScreen (4 consumer sites) and makeChatRecentActionsController (3 consumer sites) are deferred to a trivial follow-up wave.
Signature change
AccountContext protocol declaration (submodules/AccountContext/Sources/AccountContext.swift:1371) and its SharedAccountContextImpl implementation (submodules/TelegramUI/Sources/SharedAccountContext.swift:1937):
// before
func makePeerInfoController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peer: Peer,
mode: PeerInfoControllerMode,
avatarInitiallyExpanded: Bool,
fromChat: Bool,
requestsContext: PeerInvitationImportersContext?
) -> ViewController?
// after
func makePeerInfoController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peer: EnginePeer,
mode: PeerInfoControllerMode,
avatarInitiallyExpanded: Bool,
fromChat: Bool,
requestsContext: PeerInvitationImportersContext?
) -> ViewController?
Implementation body adds let peer = peer._asPeer() shadow at body-top. peerInfoControllerImpl (private, same file) and all downstream Peer-typed helpers keep raw Peer — out of scope for this wave.
public func makePeerInfoController(... peer: EnginePeer ...) -> ViewController? {
let peer = peer._asPeer()
let controller = peerInfoControllerImpl(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: fromChat)
controller?.navigationPresentation = .modalInLargeLayout
return controller
}
Consumer-side changes
73 total consumer call sites (75 raw occurrences minus 1 protocol declaration and 1 implementation). Classification (confirmed via full-repo grep):
- 58 Shape-A — inline
peer: x._asPeer()drops topeer: x. Mechanical edits. - 3 Shape-A-variant —
SettingsSearchableItems.swiftlines 1023, 1049, 1083. The upstreamguard let peer = peer?._asPeer() elsechanges toguard let peer = peer else, making the localpeerstayEnginePeer. The call-site line does not change. - 12 Shape-C — raw Peer local, add
EnginePeer(...)wrap at call site.
Shape-C site list
| File | Line | Current peer argument | New |
|---|---|---|---|
submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift |
270 | peer: peer |
peer: EnginePeer(peer) |
submodules/PeerInfoUI/Sources/ChannelMembersController.swift |
707 | peer: participant.peer |
peer: EnginePeer(participant.peer) |
submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift |
381 | peer: participant.peer |
peer: EnginePeer(participant.peer) |
submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift |
1011 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift |
4306 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift |
441 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift |
461 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift |
471 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift |
492 | peer: channel |
peer: EnginePeer(channel) |
submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift |
218 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift |
359 | peer: peer |
peer: EnginePeer(peer) |
submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift |
4362 | peer: peer |
peer: EnginePeer(peer) |
Each Shape-C wrap is a future-wave drop candidate once the raw-Peer source (stored field, participant.peer, renderedPeer.chatMainPeer, etc.) migrates upstream.
Shape-A-variant detail
SettingsSearchableItems.swift three sites share the same structure:
// before
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { peer in // peer: EnginePeer?
guard let peer = peer?._asPeer() else { // peer: Peer (shadowed)
return
}
let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer,
mode: .myProfile,
...
)
...
})
// after
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { peer in // peer: EnginePeer?
guard let peer = peer else { // peer: EnginePeer (shadowed)
return
}
let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer,
mode: .myProfile,
...
)
...
})
Files touched (≈50)
Inventoried from the grep output. Not exhaustive here; per-site enumeration lives in the implementation plan.
Signature files: AccountContext/Sources/AccountContext.swift, TelegramUI/Sources/SharedAccountContext.swift.
Shape-A consumer files (sample, not exhaustive): SelectivePrivacySettingsPeersController.swift, InstantPageControllerNode.swift, CallListController.swift, ContactsController.swift, ContactContextMenus.swift, SecureIdAuthController.swift, ChannelAdminController.swift, ChannelMembersController.swift, ChannelBannedMemberController.swift, ChannelPermissionsController.swift, MessageStatsController.swift, GroupStatsController.swift, InviteRequestsController.swift, BrowserInstantPageContent.swift, WebAppController.swift, PeersNearbyController.swift, ChatSendStarsScreen.swift, ChatRecentActionsControllerNode.swift, MiniAppListScreen.swift, JoinSubjectScreen.swift, NewContactScreen.swift, StarsTransactionScreen.swift, StoryItemSetContainerViewSendMessage.swift, StoryItemSetContainerComponent.swift, GiftViewScreen.swift, GiftOptionsScreen.swift, StorageUsageScreen.swift, TextProcessingScreen.swift, PeerInfoScreen.swift, PeerInfoScreenOpenURL.swift, JoinAffiliateProgramScreen.swift, ChatControllerScrollToPointInHistory.swift, OpenUrl.swift, OpenResolvedUrl.swift, TextLinkHandling.swift, ChatController.swift, OpenAddContact.swift, ChatManagingBotTitlePanelNode.swift, NavigateToChatController.swift, SharedAccountContext.swift (3 self-call sites), OverlayAudioPlayerControllerNode.swift, PollResultsController.swift, ChatControllerOpenWebApp.swift, ChatControllerNavigationButtonAction.swift, ChatListController.swift, ChatListSearchListPaneNode.swift.
Shape-A-variant file: SettingsSearchableItems.swift.
Shape-C-only files (other than those with mixed shapes above): BlockedPeersController.swift, ChannelBlacklistController.swift, ChatControllerOpenPeer.swift, ChatControllerLoadDisplayNode.swift.
Build/verification plan
- Apply all edits atomically. Mechanical Edit-tool string replaces for the 58 Shape-A drops; focused Edits for the 3 Shape-A-variants (guard line) and 12 Shape-C wraps.
- Full project build:
source ~/.zshrc 2>/dev/null; python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache build --configurationPath build-system/appstore-configuration.json --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 --continueOnError. - Fix any iteration-surfaced errors. Budget 2–4 iterations.
- Clean build → atomic commit with wave-39 message.
- Update
project_postbox_refactor_next_wave.mdmemory,docs/superpowers/postbox-refactor-log.md, andCLAUDE.mdwave tally. - No test runs (project has no unit tests).
Risks / watch-out
- Destructure/binding cascades. Locals named
peerdeclared asPeersomewhere in a call chain and fed tomakePeerInfoController. The body-shadow pattern contains divergence at the public API boundary, but transient Swift inference errors may surface at intermediate points. chatMainPeer/renderedPeer.peerproperty types. Shape-C sites atChatControllerNavigationButtonAction.swift:441/461/471/492andChatControllerLoadDisplayNode.swift:4362assume these properties return rawPeer. If they already returnEnginePeerin the current repo (unlikely but possible after earlier waves), the wrap should bepeer: peerwith no wrap. Verify in plan phase.- Outflow sites in Shape-C files. Some Shape-C files may have additional
peer: Peerflows elsewhere that are unrelated to this wave. Do not chase — only touch the listed sites.
Abandonment criteria
- Iteration count exceeds 5.
- A cascade requires editing
peerInfoControllerImpl(violates body-shadow boundary). - Any non-consumer file (e.g., anything in
TelegramCore,Postbox,TelegramApi) surfaces an error.
Net effect
- Public API:
AccountContext.makePeerInfoControllertakesEnginePeerinstead of rawPeer. - Bridges: -58 inline
_asPeer()+ -3 upstream-guard_asPeer()+ 12 newEnginePeer(...)wraps = net -49 bridges. - Ratchet: 12 Shape-C wraps become future-wave drop candidates (e.g.,
RenderedPeer → EngineRenderedPeermigration, participant-object migrations).
Out of scope
makeChatQrCodeScreen(4 sites),makeChatRecentActionsController(3 sites) — deferred to a trivial follow-up wave.peerInfoControllerImpland downstream Peer-typed helpers.- Shape-C source migrations (participant objects,
renderedPeer.chatMainPeer, etc.).