Commit graph

30120 commits

Author SHA1 Message Date
Ilya Laktyushin
dbd40fe7d3 Various improvements 2026-04-22 23:03:51 +02:00
isaac
1dd23f6641 Bump version 2026-04-22 03:04:50 +04:00
isaac
f77eafacff Merge commit 'f185b602ea' 2026-04-22 00:32:48 +04:00
isaac
524c29cbe3 Various improvements 2026-04-22 00:32:34 +04:00
isaac
59dc46e0f5 docs: TextStyleEditScreen caret-tracking plan
Task-by-task implementation plan for the caret-tracking auto-scroll
design. Single-file change in TextStyleEditScreen.swift: tag both text
fields, walk the transition's TextFieldComponent.AnimationHint userData
on .textChanged, compute caret rect, and adjust the enclosing
ResizableSheetComponent scroll view's bounds.
2026-04-21 23:58:07 +04:00
isaac
bf74b055cf docs: TextStyleEditScreen caret-tracking spec
Design for auto-scrolling the ResizableSheetComponent scroll view on text
change so the caret in either ListMultilineTextFieldItemComponent stays
visible above the soft keyboard.
2026-04-21 23:55:09 +04:00
isaac
2dd07ef8cc TelegramApi: regenerate Api*/SecretApiLayer*.swift with safe flag unwrap
Mechanical regeneration after the SwiftTL fix. Every
'if Int(_N!) & Int(1 << K) != 0 {...}' gate and matching
'_cM = (Int(_N!) & ...) == 0) || ...' validation in parse_<ctor>
now reads 'Int(_N ?? 0)' instead of force-unwrapping. ~2168 sites
changed across the 41 flat Api*.swift files plus the 11 layered
SecretApiLayer*.swift files. No other content change.

Full Bazel build verified green with the regenerated sources.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:44:35 +04:00
isaac
90b2ba0bd4 SwiftTL: avoid trap when flags field fails to parse
Generated parse_<ctor>(...) for constructors with conditional fields
used to emit 'if Int(_N!) & Int(1 << K) != 0 { ... }' as the gate,
and 'let _cM = (Int(_N!) & Int(1 << K) == 0) || _M != nil' as the
per-field validation. Both force-unwrap _N (the flags field read)
before the bottom-of-function '_cN = _N != nil' validation runs,
so a buffer short enough to fail the flags read traps
deterministically instead of returning nil.

Replace the force-unwraps with (_N ?? 0). Missing flags then reads
as "all bits off": every gated branch is skipped, each flag-gated
_cM short-circuits to true via the '== 0' clause, and the flags
field's own _cN = _N != nil still fails so the overall constructor
validation falls through to return nil — matching the intended
"return nil on truncated buffer" contract.

Touches both generator emit paths (flat generateImplFile and layered
emitLayeredType). Regenerated Api*/SecretApiLayer*.swift follow in a
separate commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:44:26 +04:00
Mikhail Filimonov
f185b602ea ap update 2026-04-21 20:29:39 +01:00
isaac
3559ee9f1d TelegramApi: regenerate SecretApiLayer*.swift via SwiftTL
Replaces the hand-written layer 8/46/73/101/144 files with SwiftTL
output and adds the previously-unpublished layers 17/20/23/45/66/143.
Per-layer struct names (SecretApi8, SecretApi46, ...) and public
enum case signatures are unchanged for the consumers.

The regenerated cumulative snapshots now include every constructor
declared in secret_scheme.tl, including decryptedMessageActionTyping
(layer 17) which the hand-written files had elided. Switch statements
over DecryptedMessageAction in ProcessSecretChatIncomingDecryptedOperations
gain the missing case alongside .decryptedMessageActionNoop (both are
no-ops in the consumer-side state-machine).

BUILD uses glob("Sources/**/*.swift") so no BUILD update required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 23:02:42 +04:00
isaac
e7956e0e70 Cleanup 2026-04-21 22:04:58 +04:00
isaac
da1629328f Fix resample 2026-04-21 22:04:47 +04:00
isaac
4464802051 Remove deprecated MTInputStream methods 2026-04-21 21:10:33 +04:00
isaac
2516f0005d CLAUDE.md: record full Make.py build command
Documents the actual build invocation (with codesigning + cache +
configuration arguments) at the top of the file, alongside the
existing notes that only Bazel + the codesigning env var were
mentioned. Removes the duplicate "Build environment quirk" footer
section since its content now lives at the top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:05:12 +04:00
isaac
57bfe559ea SwiftTL: main.swift branches on ParsedSchema
Flat schemas keep the existing generate(...) pipeline. Layered
schemas iterate resolveLayeredTypes and write one
{apiPrefix}Layer{N}.swift per layer via generateLayered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:56:05 +04:00
isaac
803bf23269 SwiftTL: add CodeGenerator.generateLayered for per-layer output
Emits one {apiPrefix}Layer{N}.swift file per layer: file-scope
dispatch table, public struct {apiPrefix}{N} with parse/parseVector/
serializeObject, nested public enums for each sum type using the
inline-args shape. Int256 dispatch entry emitted only when a layer's
constructors reference it. Reuses typeReferenceRepresentation /
generateFieldSerialization / generateFieldParsing unchanged, passing
the struct name as the apiPrefix so nested type refs render correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:54:09 +04:00
isaac
10054efd81 SwiftTL: add Resolver.resolveLayeredTypes
Walks layer sections in order, threads a running constructor-name map
with last-wins semantics, and snapshots [SumType] at each layer
boundary. Constructors appearing only in later layers do not leak into
earlier layers' snapshots.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:46:48 +04:00
isaac
dba8a67bcb SwiftTL: add ParsedSchema + layered schema parsing
DescriptionParser.parse(data:) now returns ParsedSchema (.flat or
.layered) based on the presence of ===N=== markers. Layered schemas
split constructor lines per layer; pre-marker constructors attach to
the lowest-numbered layer; ---functions--- is rejected in layered
mode; non-ascending markers throw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:36:09 +04:00
isaac
248c69a6d0 SwiftTL: plan for layered schema generation
Six-task implementation plan covering the parser / resolver / codegen
extension path described in the 2026-04-21 design spec. Tasks land
end-to-end, with a final Bazel build verification of the regenerated
SecretApiLayer*.swift files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:25:19 +04:00
isaac
bb29084392 SwiftTL: thread apiPrefix, drop dead CLI flags and LegacyOrderParser
Preparation for layered schema generation. Threads apiPrefix: String
through CodeGenerator.generate / generateMainFile / generateImplFile
/ typeReferenceRepresentation so the output's namespace name can
come from the --api-prefix=<prefix> CLI flag rather than a hard-coded
"Api". Drops the --stub-functions and --print-constructors=N-M flags
(both unused) and the LegacyOrderParser.swift file (obsolete migration
helper that re-parsed hand-emitted Api0.swift).

No behavior change for the flat Api*.swift output when
--api-prefix=Api (the default). Enables the upcoming layered secret-
scheme pipeline to reuse the same generator helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:24:14 +04:00
Mikhail Filimonov
365d44f03a Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios 2026-04-21 12:11:20 +01:00
Mikhail Filimonov
fe6836ab02 flag 2026-04-21 12:11:11 +01:00
isaac
25ddc21780 Update API 2026-04-21 15:09:07 +04:00
isaac
9ed75ca048 Fix build 2026-04-21 15:08:45 +04:00
isaac
afe119a73f SwiftTL: design for optional layered schema generation
Adds the design spec for extending SwiftTL to parse TL schemas
with ===N=== layer markers (secret_scheme.tl), emitting one
cumulative-snapshot Swift file per declared layer. Flat schemas
are unchanged. No CLI surface changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:47:17 +04:00
isaac
a7e9375d55 Fix sticker display 2026-04-21 14:00:16 +04:00
isaac
615becca7a Update tgcalls 2026-04-21 13:59:36 +04:00
isaac
1ef7b9732e CLAUDE.md: add TelegramEngine.Resources facade inventory + wave-shape-G recipe
Consolidates the post-wave-26 state. Adds:
- Facade inventory table covering all mediaBox facades landed through wave 26.
- Swift-stdlib-vs-third-party-module name collision lesson from wave 26.
- Wave-27+ candidates section (cached-representation triple, resourceData sweep,
  resourceStatus sweep, storageBox wrap, preferencesView, loadedPeerWithId,
  Postbox-class-move opportunities, unused-import sweep re-run).
- Wave-shape-G recipe documented: facade addition + consumer sweep in one commit,
  validated at scale across waves 19-26.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:23:48 +04:00
isaac
fa68639b25 Postbox -> TelegramEngine wave 26: resourceRangesStatus + removeCachedResources facades + sweep
Adds two TelegramEngine.Resources facades and migrates 4 Shape-A sites across 3 consumer files.
Also imports RangeSet in TelegramEngineResources.swift to disambiguate the RangeSet type from
Swift stdlib's iOS-18-only type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 04:18:03 +04:00
isaac
939873f6f4 Postbox -> TelegramEngine wave 25: copyResourceData facades + consumer sweep
Adds two TelegramEngine.Resources.copyResourceData overloads and migrates 4 Shape-A sites
across 3 consumer files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 04:09:42 +04:00
isaac
a0ba3fa29c Postbox -> TelegramEngine wave 24: moveResourceData facades + consumer sweep
Adds two TelegramEngine.Resources.moveResourceData overloads and migrates 6 Shape-A sites
across 4 consumer files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 04:04:08 +04:00
isaac
d78fe8e8b8 Postbox -> TelegramEngine wave 23: cancelInteractiveResourceFetch facade + consumer sweep
Adds TelegramEngine.Resources.cancelInteractiveResourceFetch(id:) and migrates 5 of 7 Shape-A
sites. Two sites in ChatMessageInteractiveMediaNode.swift skipped to avoid mixing with
pre-existing uncommitted WIP in that file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 03:57:46 +04:00
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