diff --git a/docs/superpowers/plans/2026-04-16-postbox-to-telegramengine-refactor-wave-1.md b/docs/superpowers/plans/2026-04-16-postbox-to-telegramengine-refactor-wave-1.md new file mode 100644 index 0000000000..2d00e69111 --- /dev/null +++ b/docs/superpowers/plans/2026-04-16-postbox-to-telegramengine-refactor-wave-1.md @@ -0,0 +1,940 @@ +# Postbox → TelegramEngine refactor, wave 1 — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Drop the direct `import Postbox` dependency from the first 10 leaf consumer submodules (one file each), routing data access through `TelegramEngine` while preserving behavior exactly. + +**Architecture:** For each of the 10 modules, apply the same deterministic playbook: inventory every Postbox reference in its single Postbox-importing file, swap bare Postbox type names for their engine typealiases (`PeerId` → `EnginePeer.Id`, etc.), replace imperative Postbox calls with existing engine methods or new thin engine wrappers added to TelegramCore in a preparatory commit, remove `import Postbox` and the Bazel dep, and run the full project build to verify. + +**Tech Stack:** Swift, Bazel (primary build system), Postbox (storage lib being made opaque), TelegramCore + TelegramEngine (the public facade), SSignalKit (signals). + +**Spec:** [docs/superpowers/specs/2026-04-16-postbox-to-telegramengine-refactor-wave-1-design.md](../specs/2026-04-16-postbox-to-telegramengine-refactor-wave-1-design.md) + +--- + +## Background the executor needs + +There are no unit tests in this project (`CLAUDE.md`: "No tests are used at the moment"). **The only verification is the full project build.** Every task ends with a full build that must go green before the next task starts. + +### The full build command + +Run from the repo root (`/Users/ali/build/telegram/telegram-ios`): + +```bash +PATH=/opt/homebrew/opt/ruby/bin:`gem environment gemdir`/bin:$PATH \ + 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 +``` + +It is slow. Do not shortcut it with `bazel build //submodules/X` — the spec chose full build per module. + +### Engine typealias cheat sheet (already in TelegramCore) + +When removing `import Postbox`, bare Postbox names in the file must be swapped for their engine equivalents. The ones that exist as typealiases today (confirmed by grep over `submodules/TelegramCore/Sources/TelegramEngine/`): + +- `PeerId` → `EnginePeer.Id` +- `MessageId` → `EngineMessage.Id` +- `MessageIndex` → `EngineMessage.Index` +- `MessageTags` → `EngineMessage.Tags` +- `MessageAttribute` → `EngineMessage.Attribute` +- `MessageFlags` → `EngineMessage.Flags` +- `MessageForwardInfo` → `EngineMessage.ForwardInfo` +- `MediaId` → `EngineMedia.Id` +- `PreferencesEntry` → `EnginePreferencesEntry` +- `TempBox` (the singleton helper) → `EngineTempBox` +- `PinnedItemId` → `EngineChatList.PinnedItem.Id` + +If a task needs a Postbox type that has **no** existing engine typealias, the task may add one in `TelegramCore` (trivial `public typealias EngineX = X`) in the preparatory commit — this is explicitly allowed by the spec. + +### Engine wrapper locations (per the spec) + +- Data reads / subscriptions → new `TelegramEngine.EngineData.Item..` in `submodules/TelegramCore/Sources/TelegramEngine/Data/Data.swift`. +- Imperative signal-returning calls → new method on `Peers` / `Messages` / `Resources` / `AccountData` under `submodules/TelegramCore/Sources/TelegramEngine//`. +- Media-resource access → extend `engine.resources` (e.g. `engine.resources.data(...)`, `engine.resources.status(...)`), forwarding to `account.postbox.mediaBox.*` internally. +- Consumer-run `account.postbox.transaction { ... }` → a specific purpose-built engine method. No generic transaction escape hatch. + +### Static-check commands (run before the build in every task) + +```bash +grep -R "^import Postbox" submodules//Sources # must return empty +grep "submodules/Postbox" submodules//BUILD # must return empty +``` + +### Commit convention + +Per module, up to two commits (optional first, required second): + +1. `TelegramCore: add ` — only if new engine wrappers were needed. +2. `: drop direct Postbox dependency` — consumer edits + BUILD change. + +Always use a HEREDOC commit body. No `--amend`. Every commit must build. + +**TelegramCore wrapper commit template** (used by any task's Step 2a when engine wrappers/typealiases are added): + +```bash +git add submodules/TelegramCore/... +git commit -m "$(cat <<'EOF' +TelegramCore: add + +Prepares for to drop Postbox. +Searched TelegramEngine/ for existing equivalents: . + + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +### Build-failure handling (applies to every task's Step 6) + +When the full build fails after a consumer edit: + +- Read the **first** compiler error in the build output. +- If it's a name-resolution or type error in the module file being refactored, fix the mapping in that file and rebuild. +- If it's in a **different** module that depends on the module being refactored, a public signature changed unexpectedly. Either (a) revert that signature change so the public surface stays identical, or (b) if the new surface is genuinely better, extend the fix to the downstream call site **in the same commit**. +- If fixing would require editing a module outside the wave-1 list, revert all changes from the current task and skip to the next fallback module listed in "The 10 modules" above. Do not pull extra modules into the wave. + +### The 10 modules (from the spec's deterministic selection rule) + +Reverse-dep count (over the 30-candidate pool) ascending, alphabetical tiebreak. Verified by running the selection script in Task 0: + +1. ActionSheetPeerItem — `submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift` +2. ChatInterfaceState — `submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift` +3. ChatListSearchRecentPeersNode — `submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift` +4. ChatSendMessageActionUI — `submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift` +5. ContactListUI — `submodules/ContactListUI/Sources/ContactListNode.swift` +6. DirectMediaImageCache — `submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift` +7. DrawingUI — `submodules/DrawingUI/Sources/DrawingScreen.swift` +8. FetchManagerImpl — `submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift` +9. GalleryData — `submodules/GalleryData/Sources/GalleryData.swift` +10. ICloudResources — `submodules/ICloudResources/Sources/ICloudResources.swift` + +If one of these hits a blocker (see spec §Risks), skip it, flag it in commit history/plan notes, and move to the next single-import candidate in the same sort order: HorizontalPeerItem, PhotoResources, PromptUI, SelectablePeerNode, TelegramIntents, ItemListAvatarAndNameInfoItem, InAppPurchaseManager, InstantPageCache, InviteLinksUI, ItemListStickerPackItem, MapResourceToAvatarSizes, PlatformRestrictionMatching, SaveToCameraRoll, ShareItems, SoftwareVideo, StickerPeekUI. The goal is 10 completed modules. + +--- + +## Task 0: Verify selection and baseline build + +**Files:** +- Read: `submodules//BUILD` + +- [ ] **Step 1: Re-run the selection script to confirm the 10** + +Save and run this Python snippet from the repo root. It should output exactly the 10 modules listed above, in that order. + +```bash +python3 <<'EOF' +import os, re +pool = ["ActionSheetPeerItem","ChatInterfaceState","ChatListSearchRecentPeersNode","ChatSendMessageActionUI","ContactListUI","DirectMediaImageCache","DrawingUI","FetchManagerImpl","GalleryData","HorizontalPeerItem","ICloudResources","InAppPurchaseManager","InstantPageCache","InviteLinksUI","ItemListAvatarAndNameInfoItem","ItemListPeerItem","ItemListStickerPackItem","MapResourceToAvatarSizes","PhotoResources","PlatformRestrictionMatching","PresentationDataUtils","PromptUI","SaveToCameraRoll","SelectablePeerNode","ShareItems","SoftwareVideo","StickerPeekUI","StickerResources","TelegramIntents","TelegramNotices"] +deps = {} +for m in pool: + p = f"submodules/{m}/BUILD" + txt = open(p).read() if os.path.exists(p) else "" + deps[m] = {o for o in pool if o != m and re.search(rf'//submodules/{re.escape(o)}(:|"|$)', txt)} +rdep = {m:0 for m in pool} +for m,ds in deps.items(): + for d in ds: rdep[d]+=1 +for m in sorted(pool, key=lambda m:(rdep[m],m))[:10]: + print(m, rdep[m]) +EOF +``` + +Expected output (one per line): `ActionSheetPeerItem 0`, `ChatInterfaceState 0`, `ChatListSearchRecentPeersNode 0`, `ChatSendMessageActionUI 0`, `ContactListUI 0`, `DirectMediaImageCache 0`, `DrawingUI 0`, `FetchManagerImpl 0`, `GalleryData 0`, `ICloudResources 0`. + +If the output differs, stop and investigate — someone changed a BUILD file since the spec was written. + +- [ ] **Step 2: Run the baseline full build** + +Run the full build command above. Expected: PASS (green master). If it fails, stop — we need a green baseline before changing anything. Do not attempt to fix pre-existing build breakage as part of this plan. + +- [ ] **Step 3: No commit** + +Task 0 produces no code changes. + +--- + +## Task 1: Refactor `ActionSheetPeerItem` + +**Files:** +- Modify: `submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift` +- Modify: `submodules/ActionSheetPeerItem/BUILD` + +**Starting inventory** (computed during planning): + +Grep for common Postbox API/type names in `ActionSheetPeerItem.swift` returned zero hits on `mediaBox`, `transaction`, `PostboxView`, `combinedView`, `PeerId`, `MessageId`, `MediaResource`, `CachedPeerData`, etc. The `import Postbox` line appears unused. Confirm this during inventory — it's the most likely case, but other Postbox symbols (e.g. types referenced inside a parameter type) may still be present. + +- [ ] **Step 1: Inventory** + +Read `submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift` top to bottom. Record every identifier that is Postbox-owned. If the inventory is empty, skip straight to Step 4. + +Run this helper grep too: + +```bash +grep -nE "\b(PeerId|MessageId|MessageIndex|MessageTags|MessageAttribute|MessageFlags|Peer|Media|MediaId|MediaResource|PostboxView|CachedPeerData|PreferencesEntry|ChatListIndex|PeerReference|TelegramMediaFile|TelegramMediaImage|Namespaces|TempBox)\b" submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift +``` + +- [ ] **Step 2: Map each reference to a replacement** + +For each finding from Step 1, decide: existing engine typealias (see cheat sheet), existing engine method, existing TelegramCore non-Postbox export, or new engine wrapper. Record the mapping in your working notes. If a new wrapper is needed, it is added in Task 1a before Task 1 continues. + +- [ ] **Step 2a: (Only if Step 2 identified a missing engine wrapper/typealias) Add to TelegramCore** + +Edit the relevant file under `submodules/TelegramCore/Sources/TelegramEngine//` or `submodules/TelegramCore/Sources/TelegramEngine/Data/Data.swift`, following the wrapper-location rules in the Background section. Keep the wrapper minimal: a single typealias for name-only adds, or a thin method that forwards to the underlying Postbox call for imperative ones. + +Run the full build. It must pass. Commit: + +```bash +git add submodules/TelegramCore/... +git commit -m "$(cat <<'EOF' +TelegramCore: add + +Prepares for ActionSheetPeerItem to drop Postbox. + + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +Skip this step if Step 2 didn't identify any missing wrappers. + +- [ ] **Step 3: Edit the consumer file** + +In `submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift`: + +- Apply every mapping from Step 2. +- Remove the line `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/ActionSheetPeerItem/BUILD`. Remove the line `"//submodules/Postbox:Postbox",` from the `deps` array. Leave the rest of the BUILD untouched. + +- [ ] **Step 5: Static checks** + +Run: + +```bash +grep -R "^import Postbox" submodules/ActionSheetPeerItem/Sources # expect: empty +grep "submodules/Postbox" submodules/ActionSheetPeerItem/BUILD # expect: empty +``` + +Both must return no output. If either produces a hit, go back to Step 3 or Step 4. + +- [ ] **Step 6: Full project build** + +Run the full build command from the Background section. Expected: PASS. + +If it fails: +- Read the first error. If it's a name-resolution error in `ActionSheetPeerItem.swift`, fix the mapping and rebuild. +- If it's in a *different* module that depends on `ActionSheetPeerItem`, you changed a public signature unexpectedly; either revert that signature change or, if it's genuinely better, extend the fix to that downstream call site in the same commit. +- If the fix would require editing a module outside the wave-1 list, revert all Task 1 changes and skip to the next fallback module listed in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/ActionSheetPeerItem/ +git commit -m "$(cat <<'EOF' +ActionSheetPeerItem: drop direct Postbox dependency + +Route data access through TelegramEngine/TelegramCore; remove the +Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 2: Refactor `ChatInterfaceState` + +**Files:** +- Modify: `submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift` +- Modify: `submodules/ChatInterfaceState/BUILD` + +**Starting inventory** (computed during planning): file references `MessageId` (×2) and `MediaResource` (×3). No `mediaBox`, `transaction`, `combinedView`, or `PostboxView` usage. This is a **type-reference-only** case — expected replacements are `MessageId` → `EngineMessage.Id` and `MediaResource` stays as-is only if a typealias exists, otherwise a typealias `EngineMediaResource = MediaResource` is added in TelegramCore. + +- [ ] **Step 1: Inventory** + +Read `submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift`. Confirm the grep below matches the planning inventory and records exact line numbers and declaration contexts (parameter types, property types, return types, generic arguments). + +```bash +grep -nE "\b(PeerId|MessageId|MessageIndex|MessageTags|MessageAttribute|MessageFlags|Peer|Media|MediaId|MediaResource|PostboxView|CachedPeerData|PreferencesEntry|ChatListIndex|PeerReference|TelegramMediaFile|TelegramMediaImage|Namespaces|TempBox)\b" submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +``` + +- [ ] **Step 2: Map each reference** + +- `MessageId` → `EngineMessage.Id` (existing typealias, no wrapper needed). +- `MediaResource`: search `submodules/TelegramCore/Sources/TelegramEngine/` for a `public typealias Engine.*Resource.*= MediaResource`. If present, use it. If absent, proceed to Step 2a and add a typealias `public typealias EngineMediaResource = MediaResource` in `submodules/TelegramCore/Sources/TelegramEngine/Resources/` (new file `EngineMediaResource.swift`, or the most natural existing file in that folder). + +- [ ] **Step 2a: (Only if needed) Add engine typealias(es) in TelegramCore** + +For each Postbox type without an engine typealias, add a `public typealias Engine = ` in the appropriate TelegramEngine area file. Do not introduce any new wrapper structs. + +Run full build, expect PASS. Commit: + +```bash +git add submodules/TelegramCore/... +git commit -m "$(cat <<'EOF' +TelegramCore: add engine typealiases for + +Prepares for ChatInterfaceState to drop Postbox. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +- [ ] **Step 3: Edit the consumer file** + +Apply the mappings from Step 2 to every reference. Remove the line `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/ChatInterfaceState/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/ChatInterfaceState/Sources # expect: empty +grep "submodules/Postbox" submodules/ChatInterfaceState/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build command. Expected: PASS. Handle failures per the rules in Task 1 Step 6. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/ChatInterfaceState/ +git commit -m "$(cat <<'EOF' +ChatInterfaceState: drop direct Postbox dependency + +Switch remaining Postbox-typed references to engine typealiases; +remove the Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 3: Refactor `ChatListSearchRecentPeersNode` + +**Files:** +- Modify: `submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift` +- Modify: `submodules/ChatListSearchRecentPeersNode/BUILD` + +**Starting inventory** (computed during planning): file uses `postbox.transaction { ... }` (×2), `postbox.combinedView(...)` (×1), and references `TelegramMedia*` types (×3). This is the **first hard module** in the wave — it has real imperative Postbox calls that require engine wrappers, not just typealiases. + +- [ ] **Step 1: Inventory** + +Read the whole file. For each Postbox call, capture: +- The call site (line number, containing function). +- The `PostboxViewKey`(s) passed to `combinedView`. +- What the closure body of each `transaction` does — the *intent*, not just the code. (This determines which engine method to add.) + +Run: + +```bash +grep -nE "\b(postbox\.|mediaBox|transaction\s*\{|combinedView|PostboxView|PostboxViewKey|Namespaces\.|TelegramMedia|PeerId|MessageId)\b" submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift +``` + +- [ ] **Step 2: Map each reference** + +- `TelegramMedia*` — these classes are defined in `TelegramCore` (check `submodules/TelegramCore/Sources/`), not Postbox. After `import Postbox` is removed they remain reachable via `import TelegramCore`, which the file already imports. No action beyond confirming. +- Each `postbox.combinedView` / view subscription → map to an existing `TelegramEngine.data.subscribe(...)` item if one exists for the same `PostboxViewKey`; if not, add an `EngineData.Item` under `submodules/TelegramCore/Sources/TelegramEngine/Data/Data.swift`. +- Each `postbox.transaction { ... }` → a specific new method on the matching engine area (e.g. `TelegramEngine.Peers.recordRecentPeer(id:)` if that's what the closure does). Do **not** add a generic transaction passthrough. + +Write the mapping down before editing. Each new engine method is small and focused. + +- [ ] **Step 2a: Add engine wrappers in TelegramCore** + +For each new `EngineData.Item` or engine method identified in Step 2: + +- Add it to the appropriate file under `submodules/TelegramCore/Sources/TelegramEngine//` (or `…/Data/Data.swift` for data items). +- Keep the body to a minimal pass-through: the new engine method opens a transaction internally and calls the same Postbox code that the consumer was running; the new `EngineData.Item` forwards a `PostboxViewKey` in `keys()` and extracts its `PostboxView` in `extract()`. +- Return engine-typed values where existing engine types are available; otherwise return primitives or `Void`. Do not return bare Postbox types. + +Before editing TelegramCore, grep for existing wrappers covering the same need: + +```bash +grep -rn "\|" submodules/TelegramCore/Sources/TelegramEngine/ +``` + +Record "searched for X, found/not found" in the commit message. + +Run the full build. Expected: PASS. + +Commit: + +```bash +git add submodules/TelegramCore/... +git commit -m "$(cat <<'EOF' +TelegramCore: add + +Prepares for ChatListSearchRecentPeersNode to drop Postbox. +Searched TelegramEngine/ for existing equivalents: not found. + + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +- [ ] **Step 3: Edit the consumer file** + +Replace each `postbox.transaction` and `postbox.combinedView` call with the engine method/subscription added in Step 2a. Swap any Postbox-typed names for engine typealiases per the cheat sheet. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/ChatListSearchRecentPeersNode/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/ChatListSearchRecentPeersNode/Sources # expect: empty +grep "submodules/Postbox" submodules/ChatListSearchRecentPeersNode/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build command. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/ChatListSearchRecentPeersNode/ +git commit -m "$(cat <<'EOF' +ChatListSearchRecentPeersNode: drop direct Postbox dependency + +Route combined-view subscription and transactions through +TelegramEngine; remove the Postbox import and Bazel dep. +Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 4: Refactor `ChatSendMessageActionUI` + +**Files:** +- Modify: `submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift` +- Modify: `submodules/ChatSendMessageActionUI/BUILD` + +**Starting inventory** (computed during planning): `mediaBox` (×2), `Peer` type reference (×1), `Media` type reference (×1), `MediaResource` (×1), `Namespaces.` (×1). The mediaBox calls are the substantive work. + +- [ ] **Step 1: Inventory** + +Read the whole file. Capture every `mediaBox` call — what resource is being asked for and what is done with the result (data read, status subscription, fetch start, path access)? Capture each Postbox-typed reference's line/context. + +```bash +grep -nE "\bmediaBox\b|\bNamespaces\.|\b(Peer|Media|MediaResource)\b" submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +``` + +- [ ] **Step 2: Map each reference** + +- Each `mediaBox.` call → `engine.resources.(...)`. Check for an existing method on `TelegramEngine.Resources` first: + ```bash + grep -rn "extension.*Resources\|public func" submodules/TelegramCore/Sources/TelegramEngine/Resources/ + ``` + If an equivalent exists, use it. If not, add one in Step 2a. +- `Peer`, `Media`, `MediaResource` type references → use `EnginePeer`, `EngineMedia`, or `EngineMediaResource` (add typealias if missing, per the cheat sheet). +- `Namespaces.Peer.` — defined in TelegramCore, not Postbox. Confirm via grep; no change needed. + +- [ ] **Step 2a: (Only if needed) Add engine wrappers in TelegramCore** + +Per the rules — minimal pass-through, return engine-typed values. Build, commit `TelegramCore: add ` per the template in Task 3 Step 2a. + +- [ ] **Step 3: Edit the consumer file** + +Apply mappings. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/ChatSendMessageActionUI/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/ChatSendMessageActionUI/Sources # expect: empty +grep "submodules/Postbox" submodules/ChatSendMessageActionUI/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/ChatSendMessageActionUI/ +git commit -m "$(cat <<'EOF' +ChatSendMessageActionUI: drop direct Postbox dependency + +Route MediaBox calls through TelegramEngine.resources and switch +type references to engine typealiases; remove the Postbox import +and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 5: Refactor `ContactListUI` + +**Files:** +- Modify: `submodules/ContactListUI/Sources/ContactListNode.swift` +- Modify: `submodules/ContactListUI/BUILD` + +**Starting inventory** (computed during planning): `postbox.transaction { ... }` (×4), `Peer` references (×15), `Namespaces.` (×1). Transactions are the substantive work; the 15 `Peer` references are likely in closures reading transaction state and will switch to engine-typed returns once the transactions are replaced. + +- [ ] **Step 1: Inventory** + +Read the whole file. For each `transaction` call, describe what the closure does — this drives what new engine methods to add. Capture every `Peer`-typed declaration. + +```bash +grep -nE "\btransaction\s*\{|account\.postbox|\b(Peer|PeerId|Namespaces)\b" submodules/ContactListUI/Sources/ContactListNode.swift +``` + +- [ ] **Step 2: Map each reference** + +- Each `postbox.transaction { ... }` → a dedicated engine method under `submodules/TelegramCore/Sources/TelegramEngine/{Peers,Contacts,AccountData}/` capturing the closure's intent. Never add a generic transaction passthrough. +- `Peer` type → `EnginePeer` where the value actually flows through the replaced engine method (the engine method should return `EnginePeer` / `[EnginePeer]`). For local variable types that receive the engine-method return, use the engine type. +- `Namespaces.*` — defined in TelegramCore. No change. + +Before adding methods, grep for existing engine functions that may already cover the intent: + +```bash +grep -rn "public func" submodules/TelegramCore/Sources/TelegramEngine/Contacts/ +grep -rn "public func" submodules/TelegramCore/Sources/TelegramEngine/Peers/ | head -60 +``` + +- [ ] **Step 2a: Add engine wrappers in TelegramCore** + +Add each new method. Return engine-typed values. Build; then use the TelegramCore wrapper commit template from the Background section. + +- [ ] **Step 3: Edit the consumer file** + +Replace every `transaction` call with its engine method. Switch `Peer` locals to `EnginePeer`. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/ContactListUI/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/ContactListUI/Sources # expect: empty +grep "submodules/Postbox" submodules/ContactListUI/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. ContactListUI is imported by other submodules (TelegramUI, SettingsUI, etc.); downstream breakage is most likely here. If a downstream consumer needs a bare `Peer`, either keep the public surface returning engine types (preferred — they're typealiases under the hood) or, if the downstream change is large, revert Task 5 and skip. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/ContactListUI/ +git commit -m "$(cat <<'EOF' +ContactListUI: drop direct Postbox dependency + +Replace direct postbox.transaction calls with dedicated engine +methods; switch peer references to engine-typed equivalents; +remove the Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 6: Refactor `DirectMediaImageCache` + +**Files:** +- Modify: `submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift` +- Modify: `submodules/DirectMediaImageCache/BUILD` + +**Starting inventory** (computed during planning): `mediaBox` (×11), `PeerReference` (×6), `MediaResource` (×1), `TelegramMedia*` (×13), `Media`/`Message` type references. This module is **mediaBox-heavy** and is the canonical shape for the `engine.resources.*` extension work. + +- [ ] **Step 1: Inventory** + +Read the whole file. For each `mediaBox` call, record the method (`resourceData`, `resourceStatus`, `cachedResourceRepresentation`, `storeCachedResourceRepresentation`, `fetchedResource`, etc.) and whether it reads, writes, or subscribes. + +```bash +grep -nE "\bmediaBox\b|\b(PeerReference|MediaResource|TelegramMedia)" submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift +``` + +- [ ] **Step 2: Map each reference** + +Each distinct `mediaBox.` signature → a method on `TelegramEngine.Resources`. Expected additions (names are suggestions — match existing naming if anything close already exists): + +- `engine.resources.data(_:pathExtension:option:attemptSynchronously:) -> Signal` +- `engine.resources.status(_:approximateSynchronousValue:) -> Signal` +- `engine.resources.cachedRepresentationData(_:representation:complete:) -> Signal<...>` +- `engine.resources.storeCachedRepresentation(_:representation:data:) -> Signal` + +Before adding any of these, grep `submodules/TelegramCore/Sources/TelegramEngine/Resources/` for existing equivalents and only add what's missing. + +`PeerReference`, `TelegramMedia*`, `MediaResource` — check each: `TelegramMedia*` types live in `TelegramCore` (not Postbox). `PeerReference` lives in `TelegramCore`. `MediaResource` is a Postbox protocol; add `EngineMediaResource = MediaResource` typealias if not already present. + +- [ ] **Step 2a: Add engine wrappers in TelegramCore** + +Add all missing methods on `Resources` and any missing typealiases. Each method is a one-line forward to `account.postbox.mediaBox.*`. Build; then commit using the TelegramCore wrapper commit template from the Background section, recording "searched Resources/ for equivalents: found/not found" in the message. + +- [ ] **Step 3: Edit the consumer file** + +Replace every `mediaBox.*` with `engine.resources.*`. This likely requires adding an `engine: TelegramEngine` parameter to a few internal functions in the file (or surfacing it from an existing `AccountContext` already in scope — prefer that). Switch types to engine typealiases. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/DirectMediaImageCache/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/DirectMediaImageCache/Sources # expect: empty +grep "submodules/Postbox" submodules/DirectMediaImageCache/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/DirectMediaImageCache/ +git commit -m "$(cat <<'EOF' +DirectMediaImageCache: drop direct Postbox dependency + +Route MediaBox calls through TelegramEngine.resources; remove the +Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 7: Refactor `DrawingUI` + +**Files:** +- Modify: `submodules/DrawingUI/Sources/DrawingScreen.swift` +- Modify: `submodules/DrawingUI/BUILD` + +**Starting inventory** (computed during planning): `transaction` (×3), `Media` type references (×13), `Namespaces.` (×4). Transactions are the substantive work; `Media`/`Namespaces` are mostly referencing TelegramCore-defined types already. + +- [ ] **Step 1: Inventory** + +Read the whole file. For each `transaction` call, describe what the closure does. Capture every `Media`-typed declaration and every `Namespaces.*` reference. + +```bash +grep -nE "\btransaction\s*\{|account\.postbox|\b(Media|MediaId|Namespaces)\b" submodules/DrawingUI/Sources/DrawingScreen.swift +``` + +- [ ] **Step 2: Map each reference** + +- Each `postbox.transaction` → dedicated engine method. Inspect the closure to find the right home (`Stickers`, `Messages`, `Peers`, …). +- `Media` → `EngineMedia` where the value flows through new engine methods; keep as `Media` (TelegramCore re-defined concrete classes like `TelegramMediaFile` live in TelegramCore and are fine) where the type is already TelegramCore's. +- `Namespaces.*` — TelegramCore. No change. + +- [ ] **Step 2a: Add engine wrappers in TelegramCore** + +Add each new transaction-replacing method. Build; then use the TelegramCore wrapper commit template from the Background section. + +- [ ] **Step 3: Edit the consumer file** + +Apply mappings. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/DrawingUI/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/DrawingUI/Sources # expect: empty +grep "submodules/Postbox" submodules/DrawingUI/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/DrawingUI/ +git commit -m "$(cat <<'EOF' +DrawingUI: drop direct Postbox dependency + +Replace direct postbox.transaction calls with dedicated engine +methods; switch type references to engine equivalents; remove the +Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 8: Refactor `FetchManagerImpl` + +**Files:** +- Modify: `submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift` +- Modify: `submodules/FetchManagerImpl/BUILD` + +**Starting inventory** (computed during planning): `mediaBox` (×8), `MediaResource` (×4), `TelegramMedia` (×1). Shape is similar to DirectMediaImageCache — heavy `mediaBox` usage, no transactions. + +- [ ] **Step 1: Inventory** + +Read the whole file. For each `mediaBox` call, record the method and direction (read / subscribe / fetch-start). + +```bash +grep -nE "\bmediaBox\b|\b(MediaResource|TelegramMedia)\b" submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift +``` + +- [ ] **Step 2: Map each reference** + +Reuse every `engine.resources.*` method added in Task 6 — do not re-add them. Grep: + +```bash +grep -rn "extension.*Resources\|public func" submodules/TelegramCore/Sources/TelegramEngine/Resources/ +``` + +If this task needs a `mediaBox` operation that Task 6 did not add (e.g. `cancelInteractiveResourceFetch`, `completeInteractiveResourceFetch`), add it now in the same pattern. + +`MediaResource` → `EngineMediaResource` typealias (already added in an earlier task if needed). `TelegramMedia` — TelegramCore type, no change. + +- [ ] **Step 2a: (Only if needed) Add missing engine methods** + +Minimal pass-through on `TelegramEngine.Resources`. Build, commit. + +- [ ] **Step 3: Edit the consumer file** + +Replace every `mediaBox.*` with `engine.resources.*`. Thread an `engine` argument through internal functions as needed (prefer reading it off an existing `AccountContext` already in scope). Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/FetchManagerImpl/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/FetchManagerImpl/Sources # expect: empty +grep "submodules/Postbox" submodules/FetchManagerImpl/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/FetchManagerImpl/ +git commit -m "$(cat <<'EOF' +FetchManagerImpl: drop direct Postbox dependency + +Route MediaBox fetch/status calls through TelegramEngine.resources; +remove the Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 9: Refactor `GalleryData` + +**Files:** +- Modify: `submodules/GalleryData/Sources/GalleryData.swift` +- Modify: `submodules/GalleryData/BUILD` + +**Starting inventory** (computed during planning): `Peer` (×1), `Media` (×9), `Message` (×4), `Namespaces.` (×3), `TelegramMedia*` (×30). No `mediaBox`, no `transaction`, no `combinedView`. This is a **type-reference-only** case at scale. + +- [ ] **Step 1: Inventory** + +Read the whole file. Record every declaration that uses a Postbox-owned type (`Peer`, `Media`, `Message`, `MessageId`, etc.) — note that `TelegramMedia*` and `Namespaces` are TelegramCore, not Postbox, and do **not** need changing. + +```bash +grep -nE "\b(Peer|Media|Message|MessageId|MessageIndex)\b" submodules/GalleryData/Sources/GalleryData.swift | head -60 +``` + +- [ ] **Step 2: Map each reference** + +- `Peer` → `EnginePeer` at the call-site level where the value is newly produced; for existing public signatures that already accept a `Peer` from elsewhere, prefer `EnginePeer` **only if** downstream consumers accept it. Otherwise leave the signature alone and swap only the internal uses. +- `Media`, `Message`, `MessageId` → engine typealiases per the cheat sheet. +- `TelegramMedia*`, `Namespaces.*` — no change. + +- [ ] **Step 2a: (Only if needed) Add engine typealiases** + +Add any missing typealias in `submodules/TelegramCore/Sources/TelegramEngine/…`. Build, commit. + +- [ ] **Step 3: Edit the consumer file** + +Apply mappings. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/GalleryData/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/GalleryData/Sources # expect: empty +grep "submodules/Postbox" submodules/GalleryData/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/GalleryData/ +git commit -m "$(cat <<'EOF' +GalleryData: drop direct Postbox dependency + +Switch Postbox-typed references to engine typealiases; remove the +Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 10: Refactor `ICloudResources` + +**Files:** +- Modify: `submodules/ICloudResources/Sources/ICloudResources.swift` +- Modify: `submodules/ICloudResources/BUILD` + +**Starting inventory** (computed during planning): `MediaResource` (×2), `TelegramMedia` (×1). No `mediaBox`, no `transaction`. Small type-reference-only module. The `MediaResource` uses may be a custom `MediaResource`-conforming class defined in this file — confirm during inventory. + +- [ ] **Step 1: Inventory** + +Read the whole file. `MediaResource` is a Postbox protocol; `ICloudResources` likely declares a custom class conforming to it. Capture whether (a) the file declares new `MediaResource`-conforming types, (b) it only references the protocol, or both. + +```bash +grep -nE "\b(MediaResource|TelegramMedia)\b|class.*:.*MediaResource|struct.*:.*MediaResource" submodules/ICloudResources/Sources/ICloudResources.swift +``` + +- [ ] **Step 2: Map each reference** + +- If the file declares a type conforming to `MediaResource`, use the `EngineMediaResource` typealias in the declaration (`class FooResource: EngineMediaResource { ... }`). Because typealiases are transparent, this keeps protocol conformance identical. +- All other `MediaResource` references → `EngineMediaResource`. +- `TelegramMedia*` — TelegramCore, no change. + +Add `EngineMediaResource` typealias in TelegramCore if not already present (Task 2 / Task 6 may have added it; check first). + +- [ ] **Step 2a: (Only if needed) Add `EngineMediaResource` typealias** + +```swift +// submodules/TelegramCore/Sources/TelegramEngine/Resources/EngineMediaResource.swift +import Postbox +public typealias EngineMediaResource = MediaResource +``` + +Build, commit. + +- [ ] **Step 3: Edit the consumer file** + +Apply mappings. Remove `import Postbox`. + +- [ ] **Step 4: Drop the Bazel dep** + +Edit `submodules/ICloudResources/BUILD`. Remove `"//submodules/Postbox:Postbox",` from `deps`. + +- [ ] **Step 5: Static checks** + +```bash +grep -R "^import Postbox" submodules/ICloudResources/Sources # expect: empty +grep "submodules/Postbox" submodules/ICloudResources/BUILD # expect: empty +``` + +- [ ] **Step 6: Full project build** + +Run the full build. Expected: PASS. Handle failures per the Build-failure handling rules in the Background section. + +- [ ] **Step 7: Commit** + +```bash +git add submodules/ICloudResources/ +git commit -m "$(cat <<'EOF' +ICloudResources: drop direct Postbox dependency + +Switch MediaResource references to EngineMediaResource; remove the +Postbox import and Bazel dep. Behavior-preserving. + +Co-Authored-By: Claude Opus 4.6 +EOF +)" +``` + +--- + +## Task 11: Wave-1 completion verification + +**Files:** No code changes. + +- [ ] **Step 1: Static check across all 10 modules** + +```bash +for m in ActionSheetPeerItem ChatInterfaceState ChatListSearchRecentPeersNode ChatSendMessageActionUI ContactListUI DirectMediaImageCache DrawingUI FetchManagerImpl GalleryData ICloudResources; do + echo "=== $m ===" + grep -R "^import Postbox" submodules/$m/Sources && echo "FAIL: import in $m" + grep "submodules/Postbox" submodules/$m/BUILD && echo "FAIL: dep in $m" +done +``` + +Expected: no `FAIL` lines printed. If any appear, return to the corresponding task and fix. + +- [ ] **Step 2: Final full build** + +Run the full build one more time from a clean state. Expected: PASS. (If it passed at the end of Task 10 and nothing else has changed, this should be cached and fast.) + +- [ ] **Step 3: Review the commit log** + +```bash +git log --oneline master..HEAD +``` + +Expected: a run of commits matching the pattern `TelegramCore: add …` (optional) and `: drop direct Postbox dependency` (one per module done). If any module was skipped per the fallback rule, verify the fallback ran and a replacement module completed so the total is 10. + +- [ ] **Step 4: No commit** + +Verification only. + +--- + +## What's explicitly NOT in this plan + +- Any edits to `TelegramCore`, `Postbox`, or the 64 modules outside the chosen 10 (except the minimum engine-wrapper / typealias additions to `TelegramCore` that the chosen modules need). +- Any new `Engine*` wrapper *structs* (only typealiases and forwarding methods are in scope this wave). +- Any generic `engine.transaction { postbox in … }` escape hatch. +- Any behavior change, performance tweak, or "while we're here" cleanup. +- Any test work — there are no tests in this project.