Commit graph

30439 commits

Author SHA1 Message Date
isaac
4879d89a92 Postbox -> TelegramEngine wave 22: storeResourceData facade + consumer sweep
Same shape as wave 21. Adds TelegramEngine.Resources.storeResourceData(id:, data:, synchronous:)
and sweeps 46 context.account.postbox.mediaBox.storeResourceData sites across 17 files.

The range-store overload and accountManager.mediaBox sites are explicitly out of scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 03:51:11 +04:00
isaac
53f023ea52 Postbox -> TelegramEngine wave 21: completedResourcePath facade + consumer sweep
Combined wave-19+wave-20 shape. Adds TelegramEngine.Resources.completedResourcePath(id:, pathExtension:)
facade and sweeps 29 consumer sites across 14 files in one atomic commit.

Shape A/B migrated. Shape C (5 sites with raw account: Account) and Shape D
(3 sites with local postbox: Postbox field) intentionally skipped — need
module-scoped init-signature rework rather than per-site sweep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 03:19:30 +04:00
isaac
7919556375 Postbox -> TelegramEngine wave 20: shortLivedResourceCachePathPrefix consumer sweep
22 call sites across 16 consumer modules migrated to the wave-19 facade:
  context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
  ->
  context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(resource.id))

ItemListStickerPackItem additionally drops the wave-18 `let rawResource` intermediate.

Skipped: MediaEditorComposerEntity.swift:245 (uses local `postbox:` init-param,
not `context.account.postbox`; needs its own wave). No modules become Postbox-free
this wave — each still has other Postbox usage.
2026-04-21 02:27:39 +04:00
isaac
7c53c9babd Postbox -> TelegramEngine wave 19: add shortLivedResourceCachePathPrefix facade
Additive-only. Adds TelegramEngine.Resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id) -> String as a thin forwarder over MediaBox.shortLivedResourceCachePathPrefix.

Unblocks consumer migration across 25+ call sites in ~15 modules; consumers migrate in follow-up waves.
2026-04-20 22:30:33 +02:00
isaac
a86376c25e Postbox -> TelegramEngine wave 18: ItemListStickerPackItem mixed-shape migration
- Add 3 narrow typealiases to TelegramCore (rule 1 — narrow utility aliases):
  EngineItemCollectionId, EngineFetchResourceSourceType, EngineFetchResourceError.
- StickerPackThumbnailItem.animated payload: MediaResource -> EngineMediaResource
  (wave-4 shape). Equatable uses EngineMediaResource.== (identical semantics).
- Replace raw fetchedMediaResource() with context.engine.resources.fetch()
  (wave-3 facade, pre-existing).
- Module becomes fully Postbox-free (source + BUILD).
2026-04-20 22:22:42 +02:00
isaac
9b06909edd Postbox -> TelegramEngine wave 17: ItemListAvatarAndNameInfoItem stateManager: collapse
Applies the wave-11/12/15 collapse pattern to ItemListAvatarAndNameInfoItem.
ItemContext.other payload changes from (accountPeerId:, postbox: Postbox,
network: Network) to (accountPeerId:, stateManager: AccountStateManager).
Internal AvatarNode.setPeer forward rewires through stateManager.postbox /
.network. Module drops import Postbox (source) and //submodules/Postbox:Postbox
(BUILD). Sole external caller — DeviceContactInfoController.swift:413 at the
Share-Extension boundary — migrated atomically.

Handle choice is stateManager: (not engine:) because the sole caller fires
specifically when the context is not a ShareControllerAppAccountContext;
ShareControllerAccountContext protocol exposes stateManager: AccountStateManager
but not engine: TelegramEngine, and no full TelegramEngine is constructible in
the Share Extension (no Account). Per feedback_postbox_refactor_handle.md and
the wave-15 precedent.

Pre-flight public-Postbox-type inventory grep (per CLAUDE.md's Wave-selection
guidance) returned only Postbox itself — no hidden leaks like wave 16's
EngineMessageHistoryThread / PeerStoryStats surprises.

Module fully Postbox-free after this wave. Running tally updated in CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:46:02 +02:00
isaac
8215c057cf CLAUDE.md: add public-Postbox-type inventory lesson to wave-selection guidance
Surfaced by wave 16 -- grepping only for Postbox/Network tokens undercounts
the hidden public-surface leaks (e.g., EngineMessageHistoryThread, PeerStoryStats).
Document the allowlist-grep recipe and the three paths for handling hits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:39:15 +02:00
isaac
8e7014a7ed CLAUDE.md: append wave 16a/16b outcome
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:31:34 +02:00
isaac
a5432e44a8 Postbox -> TelegramEngine wave 16b: ItemListPeerItem.Context engine: collapse
ItemListPeerItem.Context.Custom's init took (postbox: Postbox, network: Network)
purely to forward them to downstream AvatarNode.setPeer and EmojiStatusComponent.
Collapse to engine: TelegramEngine (wave-11 pattern, engine handle preferred per
the standing guidance since the sole external caller is main-app, not
Share-Extension).

Surface changes:
- Context.Custom: two stored fields (postbox, network) -> one (engine); init
  param pair -> single engine:.
- Context: computed postbox/network -> computed engine.
- Six internal forwards route via item.context.engine.account.postbox/.network.

External caller (sole .custom(Custom(...)) construction site codebase-wide):
- PeerInfoSettingsItems.swift:121 -> pass engine: peerAccountContext.engine
  (the accountsAndPeers tuple already carries AccountContext, which exposes
  .engine: TelegramEngine).

All 37 other ItemListPeerItem(...) sites use the .account(context: AccountContext)
convenience overload unchanged.

Module does NOT become Postbox-free: PeerStoryStats? (public surface on
storyStats) and, via the now-unavoidable Postbox import, a handful of other
transitive Postbox types remain reachable. PeerStoryStats is deeply baked into
Postbox view types (PeerView, PeerStoryStatsView, ChatListEntry, MessageHistoryView,
Postbox.getPeerStoryStats) and moving it would require a cross-module wrapper
rewrite out of scope here. EngineMessageHistoryThread.Info (the other
previously-blocking public-surface type) was moved to TelegramCore in the
preceding wave 16a commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:30:19 +02:00
isaac
3bb22d503c Postbox -> TelegramEngine wave 16a: move EngineMessageHistoryThread to TelegramCore
Promote Postbox's internal MutableMessageHistoryThreadIndexView.Item to a
public top-level type MessageHistoryThreadIndexItem; drop Postbox's empty
EngineMessageHistoryThread namespace class and its nested Item.

MessageHistoryThreadIndexView.items is now [MessageHistoryThreadIndexItem]
directly (no wrap/unwrap step in the init). The two consumer iteration
sites (ChatListNodeLocation.swift, ShareControllerNode.swift) don't need
changes -- they iterate without type annotation and only read fields that
exist on both the old nested Item and the new top-level type.

Collapse TelegramCore's public extension EngineMessageHistoryThread into
a proper class definition:
public final class EngineMessageHistoryThread { class Info { ... } }.

After this, EngineMessageHistoryThread is a pure TelegramCore type --
consumers that only use EngineMessageHistoryThread.Info no longer need
import Postbox for the outer name to resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:26:32 +02:00
isaac
a053909b87 Fix crash 2026-04-20 21:06:20 +02:00
isaac
46566e7155 Postbox -> TelegramEngine wave 15: SelectablePeerNode stateManager collapse
Applies the wave-11/12 AccountStateManager collapse pattern to
SelectablePeerNode. Module is now fully Postbox-free (source + BUILD).

- SelectablePeerNode.setup(... postbox: Postbox, network: Network, ...)
  -> setup(... stateManager: AccountStateManager, ...). Same for
  setupStoryRepost. AvatarNode.setPeer and EmojiStatusComponent are
  forwarded as stateManager.postbox / .network without naming Postbox.
- Three Namespaces.Peer.SecretChat == checks rewritten to use the
  existing PeerId.isSecretChat extension (as in wave 13).
- ShareControllerPeerGridItem.setup / setupStoryRepost and
  HorizontalPeerItem.setup call sites collapse to stateManager:.
  JoinLinkPreviewPeerContentNode uses the convenience
  setup(context: AccountContext) and is unchanged.
- Drop import Postbox in SelectablePeerNode.swift and the
  //submodules/Postbox:Postbox dep in SelectablePeerNode/BUILD.

The stateManager fallback (over the normally-preferred engine:
TelegramEngine) is used because SelectablePeerNode crosses the Share
Extension boundary: ShareControllerAccountContextExtension has no
Account, so TelegramEngine(account:) is physically unreachable there.
This matches the "rare but genuine fallback" clause of
feedback_postbox_refactor_handle.md.

Build verified green (debug_sim_arm64, 193 actions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:37:41 +02:00
isaac
b20cd3502b Postbox -> TelegramEngine waves 11-14: stateManager collapse + BUILD-dep sweep
Wave 11: ActionSheetPeerItem de-Postboxed. Revisits wave-1 abandonment.
`postbox: Postbox, network: Network` init params collapse to
`stateManager: AccountStateManager`; avatar setPeer call routes via
`item.stateManager.postbox` / `.network`. Module never names Postbox.
Sole caller (ShareController.swift:1146) migrated in place.

Wave 12: HorizontalPeerItem de-Postboxed (same pattern). Ripples the
collapse up into ChatListSearchRecentPeersNode's public init
(`postbox:/network:` -> `stateManager:`). That module still imports
Postbox for PostboxViewKey/UnreadMessageCountsView internals but its
public surface simplifies. 3 external caller sites migrated.

Wave 13: AttachmentTextInputPanelNode minor cleanup. Module was already
Postbox-free at source level (wave 6) but carried a dead BUILD dep and
had 2 raw `peerId?.namespace == Namespaces.Peer.SecretChat` checks.
Both now use existing `PeerId.isSecretChat` extension in TelegramCore.

Wave 14: BUILD-dep sweep mirroring wave 6's source sweep. 98 modules
had `//submodules/Postbox:Postbox` (or `//submodules/Postbox`) BUILD
deps despite no source file importing Postbox since wave 6. Single
iteration, zero restores -- Bazel Swift requires source-level `import`
for symbol resolution, so redundant BUILD deps are pure metadata.

Net: 110 files, +116/-149. Build verified green (debug_sim_arm64).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:26:58 +02:00
isaac
3716f59e7d Cleanup 2026-04-20 18:55:27 +02:00
isaac
0fd2765908 DecryptMatch: pure-Python port of fastlane match decrypt.rb
Replace the Ruby decrypt.rb shell-out with a direct Python call to
decrypt_match_data(). The iOS build no longer depends on a Ruby
interpreter. Includes the spec, plan, AES-256 port, tightened error
surfaces for key length and V1 fallback, and the BuildConfiguration
wire-up that drops decrypt.rb.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:45:34 +02:00
Isaac
34c2c8c8a4 Postbox -> TelegramEngine wave 10: StorageFileListPanelComponent drop
Closes the last future-wave candidate from wave 8 by eliminating the
Icon.media(Media, ...) enum case in StorageFileListPanelComponent.swift
and dropping import Postbox. StorageUsageScreen (the module as a whole)
is now fully Postbox-free.

Icon enum split:
  case media(Media, TelegramMediaImageRepresentation)  ->
    case mediaFile(TelegramMediaFile, TelegramMediaImageRepresentation)
    case mediaImage(TelegramMediaImage, TelegramMediaImageRepresentation)

Equatable rewritten as switch-over-tuple with id-based equality per
concrete type (lFile.fileId == rFile.fileId / lImage.imageId ==
rImage.imageId), same semantics as the old media.id comparison.

Binding site: `if case let .media(media, representation)` +
`as? TelegramMediaFile` / `as? TelegramMediaImage` downcasts ->
compound case-binding `case let .mediaFile(_, representation), let
.mediaImage(_, representation):` to lift the shared representation
variable, plus an inner switch for the setSignal branch. The compiler-
enforced exhaustiveness of the split improves call-site safety.

Construction sites (2): `.media(file, representation)` -> `.mediaFile(
file, representation)`, `.media(image, representation)` -> `.mediaImage(
image, representation)`.

Placeholder fixup:
  messageId: EngineMessage.Id(peerId: PeerId(namespace: PeerId.Namespace
    ._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)
    ), namespace: 0, id: 0)
  ->
  messageId: EngineMessage.Id(peerId: component.context.account.peerId,
    namespace: 0, id: 0)

Inside a measureItem layout-measurement instance. Caught by second-pass
build failure `cannot find 'PeerId' in scope`. PeerId / PeerId.Namespace
/ PeerId.Id are raw Postbox types (not TelegramCore typealiases —
consistent with wave 9's MessageId -> EngineMessage.Id fixup). Using
context.account.peerId is semantically equivalent for the measurement
use case (messageId only feeds image-fetch userLocation and Equatable
comparison, neither exercised for this standalone instance).

Net: 1 file changed, +22 / -29 lines.

Plan: docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-10.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 01:46:18 +02:00
Isaac
1a432362a3 Postbox -> TelegramEngine wave 9: StorageUsageScreen preferences-view rewrite
Closes the first of the two future-wave candidates left open by wave 8 by
rewriting both AccountSpecificCacheStorageSettings preferences-view
observation sites in StorageUsageScreen.swift using engine APIs, and
drops import Postbox from that file.

Site 1 — cacheSettingsExceptionCount (former 1047-1087):
  postbox.combinedView(keys: [.preferences(keys: Set([...]))]) +
  PreferencesView                ->
    context.engine.data.subscribe(TelegramEngine.EngineData.Item
                                    .Configuration.ApplicationSpecific-
                                    Preference(key: ...))
                                   + preferencesEntry?.get(...)

Site 2 — peerExceptions (former 3131-3196):
  - Same preferences-observation replacement as Site 1.
  - postbox.transaction { transaction.getPeer / getPeerCachedData as?
    CachedGroupData / CachedChannelData; FoundPeer(peer:subscribers:) }
    -> context.engine.data.get(EngineDataMap(...TelegramEngine.Engine-
       Data.Item.Peer.Peer.init(id:))) + pattern match on EnginePeer
       .user / .secretChat / .legacyGroup / .channel
  - Signal element: [(peer: FoundPeer, value: Int32)] -> [(peer: Engine-
    Peer, value: Int32)]. FoundPeer wrapper and its `subscribers` field
    dropped — computed but never read downstream (consumers only read
    .isEmpty, .count, and .prefix(3).map { EnginePeer($0.peer.peer) }).

Consumer update:
  peerExceptions.prefix(3).map { EnginePeer($0.peer.peer) } ->
      .prefix(3).map { $0.peer }

Typealias fixup (caught by first-pass build failure):
  var mergedMedia: [MessageId: Int64] -> [EngineMessage.Id: Int64]
  (MessageId is raw Postbox; must use the EngineMessage.Id typealias
   when import Postbox is removed.)

Reusable pattern documented in CLAUDE.md: TelegramEngine.EngineData.Item
.Configuration.ApplicationSpecificPreference(key: ValueBoxKey) is the
general-purpose engine replacement for the postbox.combinedView(keys:
[.preferences(keys: Set([key]))]) + PreferencesView idiom. Works from
any module importing TelegramCore (without import Postbox) because
passing PreferencesKeys.<name> keeps ValueBoxKey as an inferred-only
type that never gets named in the consumer.

Net: 1 file changed, +30 / -54.

StorageUsageScreen.swift is now Postbox-free. The wave 8 outcome's other
future candidate (StorageFileListPanelComponent.swift's Icon.media(Media,
...) enum case) remains — trivial future wave will land the whole-module
drop.

Plan: docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-9.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 01:11:50 +02:00
Isaac
635d392484 Postbox -> TelegramEngine wave 8: StorageUsageScreen domain-type migration
Narrow consumer-module migration of raw Message types to EngineMessage in
the StorageUsageScreen component. Two files touched; the module keeps
import Postbox because of two out-of-scope site clusters (preferences-
view observation + a Media-carrying Icon enum case) — both flagged in
CLAUDE.md and the wave doc as future-wave targets.

StorageUsageScreen.swift:
  SelectionState.togglePeer(availableMessages:)
      [EngineMessage.Id: Message]  -> [EngineMessage.Id: EngineMessage]
  AggregatedData.messages
      [MessageId: Message]         -> [EngineMessage.Id: EngineMessage]
  AggregatedData.clearIncludeMessages / .clearExcludeMessages
      [Message]                     -> [EngineMessage]
  AggregatedData.init messages param — same swap
  RenderResult.messages
      [MessageId: Message]         -> [EngineMessage.Id: EngineMessage]
  openMessage(message: Message)    -> openMessage(message: EngineMessage)
                                      (unwrap to raw at OpenChatMessageParams
                                       / chatMediaListPreviewControllerData
                                       call sites via ._asMessage())

StorageFileListPanelComponent.swift:
  Item.message: Message            -> EngineMessage
                                      (internal .id / .timestamp / .media
                                       usage compiles unchanged against the
                                       EngineMessage class).

Wave-7 facade-boundary bridging dropped:
  - renderStorageUsageStatsMessages call site: the .mapValues(EngineMessage.init)
    on existingMessages and .mapValues { $0._asMessage() } on the result vanish;
    AggregatedData.messages and RenderResult.messages are now engine-typed on
    both sides of the facade.
  - clearStorage call sites (2): the .map(EngineMessage.init) wraps around
    includeMessages / excludeMessages vanish; locals become [EngineMessage].
  - Inside AggregatedData.updateSelected... accumulation loop, four
    item.message._asMessage() calls (where item.message was EngineMessage
    and the target was [Message]) drop back to plain item.message.
  - StorageMediaGridPanelComponent.Item(message: EngineMessage(message), ...)
    at the RenderResult-build loop loses the EngineMessage(...) wrap since
    `message` is already EngineMessage.

Out of scope (module keeps import Postbox):
  - StorageUsageScreen.swift:1047-1062 / 3131-3185 — AccountSpecificCache-
    StorageSettings observation via postbox.combinedView + PreferencesView
    and a postbox.transaction block doing transaction.getPeer / getPeerCached-
    Data as? CachedGroupData/CachedChannelData for peer-category classification.
  - StorageFileListPanelComponent.swift:105 — Icon.media(Media, ...) enum case.
    Constructed only as .media(TelegramMediaFile, ...) or .media(TelegramMedia-
    Image, ...); trivial future wave to split into two cases.

Build verified green: 59s incremental, 27 actions, Telegram.ipa produced.

Plan: docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-8.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 00:39:19 +02:00
Isaac
b2ba4b5878 Postbox -> TelegramEngine wave 7: last TelegramEngine.* facade leaks
Closes the seven remaining raw-Postbox leaks in TelegramEngine public
facades surfaced by a post-wave-6 scouting pass (all non-permanently-
blocked candidates).

Facades migrated in place (6 rewrites + 1 deletion; all _internal_*
implementations unchanged per the "internal Postbox-facing stays raw"
rule):

  Messages:
    downloadMessage                          Signal<Message?>           -> Signal<EngineMessage?>
    topPeerActiveLiveLocationMessages        Signal<(Peer?, [Message])> -> Signal<(EnginePeer?, [EngineMessage])>
    getSynchronizeAutosaveItemOperations     deleted (dead facade; sole caller uses _internal_ directly)

  Peers:
    updatedRemotePeer                        Signal<Peer>  -> Signal<EnginePeer>
                                             (PeerReference param kept; no EnginePeer.Reference alias today)

  Resources:
    renderStorageUsageStatsMessages          [EngineMessage.Id: Message]  -> [EngineMessage.Id: EngineMessage]
    clearStorage(peerId: ...)                [Message]                     -> [EngineMessage]
    clearStorage(peerIds: ...)               [Message]                     -> [EngineMessage]
    clearStorage(messages:)                  [Message]                     -> [EngineMessage]
                                             (no external callers; migrated for overload-set consistency)

Consumer call-site updates (5 files):
  - ChatListSearchListPaneNode       drop redundant .flatMap(EngineMessage.init) wrap
  - LocationViewControllerNode       drop redundant .map(EngineMessage.init) wrap
  - LiveLocationSummaryManager       drop redundant EnginePeer(...) / EngineMessage(...) ctors
  - StorageUsageScreen               bridge [Message] <-> [EngineMessage] at the 4 facade-call points
                                     (internal [MessageId: Message] / [Message] storage kept;
                                      full-consumer-module migration is out of scope)

Discovery: grep of TelegramEngine/*/TelegramEngine*.swift public signatures
for `: Postbox|: Account|: MediaBox|: MediaResource|: Peer\b|: Message\b|
-> Signal<.*(Peer|Message)` turned up these seven candidates and no others.
After this wave, the full TelegramEngine.* facade surface is engine-typed
modulo the four permanently-blocked TelegramMediaResource-conforming
classes recorded in CLAUDE.md (ICloudFileResource, InstantPageExternal-
MediaResource, VideoLibraryMediaResource, YoutubeEmbedStoryboardMedia-
Resource).

No modules became Postbox-free in this wave. Plan: docs/superpowers/
plans/2026-04-20-postbox-to-telegramengine-wave-7.md.

Full project build verified green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 00:25:28 +02:00
Isaac
31012e7cf9 CLAUDE.md: update wave-6 commit SHA references after squash
The wave-6 commits (spec, plan, C1 batch drop, C2 tally) were
squashed into a single commit, shifting the SHA referenced twice in
the wave-6 outcome section. Updates the two "see git show <sha>"
pointers to the new squashed commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:55:25 +02:00
Isaac
8edaa79eda Merge commit '23c4d94748' 2026-04-19 23:54:22 +02:00
Isaac
7b2b74e79b Postbox -> TelegramEngine wave 6: unused import Postbox batch sweep
First build-verified unused-import sweep: speculatively dropped
import Postbox from 782 consumer files (plain ^import Postbox$ lines,
excluding TelegramCore/Postbox/TelegramApi paths), iterated 18 full
project builds with --continueOnError, restored the import on every
file that failed to compile. 183 drops survived; 189 consumer modules
newly Postbox-free.

Bundled: spec + plan + C1 atomic batch drop + C2 CLAUDE.md outcome and
permanent methodology guidance under Wave-selection. The methodology
subsection captures the reusable playbook (--continueOnError is
essential, dependency graphs are deep so expect many iterations,
pattern-based preemptive restores accelerate convergence, and
CLAUDE.md's engine typealias cheat sheet arrows are migration targets
rather than typealiases in TelegramCore).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:46:13 +02:00
Isaac
c99adbbe0b Postbox -> TelegramEngine wave 5: uploadSecureIdFile + SecureId context
Migrates uploadSecureIdFile's public signature from (context:,
postbox: Postbox, network: Network, resource: MediaResource) to
(context:, engine: TelegramEngine, resource: EngineMediaResource).
SecureIdVerificationDocumentsContext refactored to hold
engine: TelegramEngine instead of raw postbox + network, drops
import Postbox (tally +1). The one instantiation site in
SecureIdDocumentFormControllerNode updates to pass
engine: self.context.engine.

Completes the last explicitly-named future-wave candidate from
CLAUDE.md.

Bundled: spec + plan + C1 atomic migration + CLAUDE.md outcome.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:59 +02:00
Isaac
b989b160c0 Postbox -> TelegramEngine wave 4: TelegramEngine.Stickers.uploadSticker
Migrates uploadSticker's public surface from (peer: Peer,
resource: MediaResource, thumbnail: MediaResource?, ...) to
(peer: EnginePeer, resource: EngineMediaResource,
thumbnail: EngineMediaResource?, ...), and UploadStickerStatus.complete
payload from CloudDocumentMediaResource to EngineMediaResource. Both
external call sites plus the cascading destructure and .standalone
sites inside MediaEditorScreen migrated atomically.

Bundled: spec + fix + plan + C1 atomic facade migration + CLAUDE.md
outcome + enum-payload inventory lesson added to wave-selection
guidance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:48 +02:00
Isaac
4ae15b42a7 Postbox -> TelegramEngine wave 3: MediaBox fetch/status/data facades + SaveToCameraRoll
Adds three thin forwarding methods on TelegramEngine.Resources
(fetch, status, data) over MediaBox, then migrates SaveToCameraRoll's
three public functions to use them, drops import Postbox from the
module (source + Bazel dep), and updates all 23 call sites across 14
caller files atomically.

Bundled: spec + fix + plan + C1 facades + C2 SaveToCameraRoll rewrite
+ BUILD dep drop + CLAUDE.md outcome.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:34 +02:00
Isaac
791bb62148 Display/ListView: snap-scroll to pinned item + synchronous history transactions
Adds an experimentalSnapScrollToPinnedItem path that resnaps scroll to
the first pinToEdgeWithInset item after inset/visible-size updates and
on empty itemNodes. Promotes ListViewItem to AnyObject and adds a
pinToEdgeWithInset protocol requirement with a default of false.

Also runs chat history transactions synchronously when updating scroll
around correlated messages, and tightens beganInteractiveDragging weak-
self handling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:18 +02:00
Isaac
684796144c TextProcessing: style create/edit/delete, AI previews, and integration
Wires up TextProcessingScreen to the edit/delete AI text style facades,
renders AI text style webpage previews in chat bubbles, and exposes a
shared makeEmojiStatusSelectionController on SharedAccountContext so
the screen can present the emoji picker when assigning a style emoji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:18 +02:00
Isaac
d01bbc6529 TelegramCore: edit/delete AI text styles + AI webpage attribute
Adds TelegramEngine.Messages.editAITextStyle and deleteAITextStyle
facades and decodes webPageAttributeAiComposeTone into a new
TelegramMediaWebpageAITextStyleAttribute. Makes CloudStyle.prompt
optional and requires emojiFileId on create.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:18 +02:00
Isaac
b8e985b939 TelegramApi: regenerate schema for AI-compose tone + webpage attribute
Adds aicompose.createTone/updateTone/deleteTone functions,
InputAiComposeTone constructor, and webPageAttributeAiComposeTone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:18 +02:00
Isaac
4bd54b9771 Display/ListView: pin first pinToEdgeWithInset item to bottom edge
Introduces a pin-to-edge mode for list items: items whose
pinToEdgeWithInset flag is true are pinned to the bottom edge of the
visible area, with the list geometry (pin inset, scrollToItem,
inset-transition offsetFix, visible-size updates) adjusted to handle
this new mode consistently.

Collected from spec, plan, and 12 iterative fixes during
implementation that refined pin-inset helpers, scroll landings, and
transition math. Pin-inset computation uses frame.size.height;
scrollToItem lands on the item's pinned position via apparentBounds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:45:13 +02:00
Ilya Laktyushin
23c4d94748 Various fixes 2026-04-19 21:50:24 +02:00
Ilya Laktyushin
1670677a92 Various fixes 2026-04-19 21:48:47 +02:00
Ilya Laktyushin
b138b9f549 Stickers & emoji search pagination 2026-04-19 18:32:40 +02:00
Ilya Laktyushin
c9b76c9294 Various fixes 2026-04-19 14:17:48 +02:00
Ilya Laktyushin
d88a29b378 Various improvements 2026-04-18 08:41:08 +02:00
Ilya Laktyushin
1fa0c7991c Various fixes 2026-04-17 11:06:56 +02:00
Isaac
bedbc8de6d CLAUDE.md: record wave-2 outcome and update wave-selection guidance
Reflects the current state of the Postbox → TelegramEngine refactor:

- Header now lists waves 1 and 2 with their plans.
- New "Wave 2 outcome (2026-04-17)" section enumerates the five
  migrated TelegramEngine facades, the one fully de-Postboxed
  consumer submodule (MapResourceToAvatarSizes), the AuthorizationUI
  signal type swap, and the abandoned Task 8 (SaveToCameraRoll).
- Wave-selection guidance extended with two lessons learned:
  (a) planning-time inventories should grep the full Postbox token
  set, not just the tokens specific to the wave's goal, to avoid
  execution-time abandonments like wave 2's SaveToCameraRoll; and
  (b) facade-API migrations (wave 2's shape) work better than
  per-module Postbox drops (wave 1's shape) when the target is a
  shared API surface.
- New "Modules currently free of import Postbox" tally lists the
  8 consumer modules now clean across all waves and the standalone
  PromptUI / PresentationDataUtils / StickerPeekUI cleanups.
- New "Known future-wave candidates" section records the three
  leaks surfaced by the wave-2 final review and the four permanently
  blocked TelegramMediaResource-conforming classes.

Docs-only change. No behavior change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:43:48 +02:00
Isaac
bbfd1ddca0 Merge commit '532a3ae3e1' 2026-04-17 09:09:55 +02:00
Isaac
f84e94f507 MediaResource → EngineMediaResource refactor: wave 2
Drive raw `MediaResource` out of the `TelegramEngine` public facade for
photo-upload APIs, and complete a first batch of consumer-side
migrations. Behavior-preserving. The `_internal_*` Postbox-facing
functions are untouched — only the facade signatures change, bridging
inside via `_asResource()` / `EngineMediaResource(_:)`.

TelegramEngine facades migrated (signatures now take EngineMediaResource):
- TelegramEngine.Peers.uploadedPeerPhoto / uploadedPeerVideo
- TelegramEngine.Peers.updatePeerPhoto (closure param too)
- TelegramEngine.AccountData.updateAccountPhoto / updateFallbackPhoto
- TelegramEngine.Contacts.updateContactPhoto
- TelegramEngine.Auth.uploadedPeerVideo

Consumer-side changes:
- MapResourceToAvatarSizes utility: signature is now
  (engine: TelegramEngine, resource: EngineMediaResource, ...). Uses
  engine.resources.data(id:) internally. The submodule drops
  `import Postbox` and the Bazel dep.
- AuthorizationUI: avatar-video signal retyped from
  Signal<TelegramMediaResource?> to Signal<EngineMediaResource?>.
- 27 `mapResourceToAvatarSizes(postbox:...)` call sites across 5
  TelegramUI/TelegramCallsUI files migrated to the new form.

Deferred:
- SaveToCameraRoll (planned Task 8) abandoned — module has three
  public functions taking `postbox: Postbox` (umbrella-type leak,
  banned by rule 2) and requires a full module-migration wave, not a
  type swap. Reason recorded in the wave-2 plan doc.
- Other TelegramEngine facades still leaking MediaResource
  (TelegramEngineStickers.uploadSticker; UploadSecureIdFile.* —
  additionally leaks Postbox) flagged for a future wave.

Docs:
- CLAUDE.md: new rule 7 (TelegramCore never imports UIKit/Display,
  shared with Telegram-Mac), and a new "MediaResource →
  EngineMediaResource consumer migration" section describing the
  wrap/unwrap helpers and the modify-in-place facade-bridging
  pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 09:07:54 +02:00
Isaac
adc275af27 Fix base64 2026-04-17 09:06:14 +02:00
Isaac
75ec477248 PresentationDataUtils: drop direct Postbox dependency
The only Postbox usage in Sources/ was a `PeerId?` parameter in the
public signature of openUserGeneratedUrl. Switch to the
EnginePeer.Id typealias (same type as PeerId, so no call sites
break) and remove the Postbox import. Add TelegramCore to BUILD
deps since EnginePeer lives there; Postbox was not in BUILD (was
transitively supplied). Behavior-preserving.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 23:55:52 +02:00
Isaac
ef0974de6d PromptUI: drop unused Postbox dependency
The `import Postbox` line and `//submodules/Postbox` BUILD dep were
unused — no Postbox types or APIs are referenced in the module.
Removing both. Behavior-preserving.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 23:55:48 +02:00
Isaac
10e40b974c StickerPeekUI: drop unused Postbox dependency
The `import Postbox` line and `//submodules/Postbox` BUILD dep were
unused — no Postbox types or APIs are referenced in the module.
Removing both. Behavior-preserving.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 23:37:44 +02:00
Isaac
ad85ef6517 Postbox → TelegramEngine refactor: wave 1
Drop direct `import Postbox` from four leaf consumer submodules, routing
data access through TelegramEngine/TelegramCore. Behavior-preserving.

Consumers migrated:
- ChatInterfaceState
- ChatSendMessageActionUI
- ContactListUI
- DrawingUI

TelegramCore additions (typealiases + one EngineData item):
- EngineMemoryBuffer, EnginePostboxDecoder, EnginePostboxEncoder,
  EngineAdaptedPostboxDecoder (narrow utility aliases for coding support)
- EngineData.Item.Configuration.ContactsSettings

Six planned modules were abandoned in wave 1 with reasons recorded in
the plan: ActionSheetPeerItem, ChatListSearchRecentPeersNode,
DirectMediaImageCache, FetchManagerImpl, GalleryData, ICloudResources.
Each either has a public API that leaks `Postbox`/`Account`/`MediaBox`
(banned umbrella-type aliases) or cascades into out-of-wave modules.

CLAUDE.md now records the wave rules, typealias cheat sheet, and the
wave-selection guidance learned from this pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 23:16:45 +02:00
Ilya Laktyushin
532a3ae3e1 Various fixes 2026-04-16 21:45:26 +02:00
Isaac
40112d3cff Add implementation plan: Postbox → TelegramEngine wave 1
10 tasks, one per chosen leaf module, each with inventory-map-edit-
build-commit steps. Common build command, commit template, and
failure-handling rules centralized in a Background section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 18:32:51 +02:00
Isaac
ec24ec7dbb Tighten wave-1 spec: clarify engine typealias rules
TelegramCore does not re-export Postbox, so dropping the Postbox
import forces every Postbox-type reference to switch to an engine
typealias. Spell this out, and clarify that new typealiases are in
scope while new engine wrapper structs are not.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 18:24:13 +02:00
Isaac
5d40b8ca33 Add design: Postbox → TelegramEngine refactor, wave 1
Covers the first 10 single-import leaf submodules, in bottom-up
dependency order, with full-build verification per module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 18:22:52 +02:00
Isaac
bf21bca206 Merge commit '31e3a7dc79'
# Conflicts:
#	submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift
#	submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift
#	submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift
2026-04-16 17:52:38 +02:00
Ilya Laktyushin
31e3a7dc79 Fix build 2026-04-16 02:36:03 +02:00