chore: prepare wintergram 1.1 release

This commit is contained in:
PAVEL IVANOV 2026-06-15 21:46:25 -07:00
parent c37205ebc7
commit f6bb33d193
253 changed files with 21000 additions and 67780 deletions

153
docs/FEATURES.md Normal file
View file

@ -0,0 +1,153 @@
# WinterGram Features
This document describes every user-facing WinterGram capability. Settings are grouped under the **WinterGram** tab in the app (Settings → WinterGram).
---
## Ghost Mode
Control what activity signals you send to Telegram servers.
| Feature | Description |
| :-- | :-- |
| Ghost Mode presets | Off, Messages only, Stories only, or Everything |
| Send without sound | Never, only in Ghost Mode, or always |
| Scheduled send trick | Route ghost-mode sends through the scheduled path so the chat stays unread |
| Track online status | Local log of when contacts go online (Online Tracker screen) |
| Story ghost | View stories without marking them seen |
| Mark read after action | Mark chats read locally after you reply or react, without sending receipts |
| Go offline after online | Send a one-shot offline presence after appearing online |
---
## History and Recovery
Keep message content on this device even when it is removed or changed on the server.
| Feature | Description |
| :-- | :-- |
| Save deleted messages | Retain messages after the other side deletes them |
| Save edit history | Store every revision of edited messages |
| Save from bots | Include bot messages in the deleted-message cache |
| Save self-destruct | Keep disappearing messages before they vanish |
| Dim deleted messages | Render saved deletions at reduced opacity |
| Clear saved deletions | Remove all locally cached deleted messages |
| Deleted messages browser | Data & Storage → Deleted Messages: pie chart by type, per-category cleanup, **top chats** by deletion count |
---
## Hidden Archive
A separate stash for chats that should not appear in the main list.
| Feature | Description |
| :-- | :-- |
| Stash chats | Move chats to a settings-only archive via context menu |
| Stashed chats list | Browse and unstash from WinterGram → Core → Hidden Archive |
| Mute notifications | Drop in-app banners for stashed peers |
| Auto mark as read | Mark incoming stashed messages read locally |
| Passcode | Optional passcode gate before opening the stash |
---
## Features (anti-features and conveniences)
| Feature | Description |
| :-- | :-- |
| Disable ads | Hide sponsored messages in channels |
| Local Premium | Unlock Premium-gated UI locally (no server-side subscription) |
| Hide stories | Remove stories from the chat list header |
| Hide Premium statuses | Conceal Premium badges and statuses |
| Disable open-link warning | Skip the concealed-URL confirmation dialog |
| Allow saving restricted content | Bypass copy-protection on protected media |
| Allow screenshots everywhere | Disable screenshot blocking in supported views |
| Confirm stickers / GIFs / voice | Ask before sending each media type |
---
## Chat
| Feature | Description |
| :-- | :-- |
| Show message seconds | Display seconds in message timestamps |
| Show peer ID | Hidden, Telegram API form, or Bot API form in profiles |
| Show registration date | Display account creation date where available |
| Hide edited mark | Suppress the "edited" label on messages |
| Message translation | Context-menu translation with selectable provider |
| Translation provider | Telegram, Google, Yandex, or system |
| Increase WebView height | Taller viewport for mini-apps |
| Only added emoji and stickers | Limit picker to your custom sets |
| Forward without author | Omit original author on forwards |
| Default reaction | Custom emoji reaction used by default |
---
## Appearance
| Feature | Description |
| :-- | :-- |
| Material Design | Material-styled switches and controls |
| Single corner radius | Apply bubble radius to one corner only |
| Avatar shape | Slider from square through squircle to round |
| Bubble radius | Adjustable message bubble corner radius |
| Custom font | Override the UI typeface |
| Monospace font | Override the code/monospace typeface |
| Icon pack | WinterGram, Ayu, exteraGram, or Telegram alternate icons |
| Liquid Glass | Frosted translucent surfaces with per-area toggles |
| Liquid Glass vibrancy | Extra vibrancy on glass layers |
| Apply to chat list / nav bars / tab bar / bubbles | Per-surface glass control |
---
## Spoofing
Report different device metadata to Telegram and mini-apps. Changing API credentials requires re-login.
| Feature | Description |
| :-- | :-- |
| Spoof device model | Override the hardware identifier |
| Spoof app version | Override the client version string |
| WebView platform | Automatic, iOS, Android, macOS, or desktop |
| Custom API ID / Hash | Use your own credentials from my.telegram.org |
| Spoof presets | Save and recall device/version profiles |
---
## Badges and Branding
| Feature | Description |
| :-- | :-- |
| WinterGram badge | Composed at runtime from white backplate + snowflake shapes, tinted to the theme |
| Developer badge | Backplated snowflake for contributors |
| Official channel badge | Snowflake for official WinterGram resources |
| Use default branding | Restore standard Telegram naming in the UI |
---
## Accounts and Data
| Feature | Description |
| :-- | :-- |
| Multiple accounts | Up to 100 accounts on one device |
| Deep links | `wnt://` scheme mirrors `tg://`; `wnt://wintergram/<section>` opens a settings category |
| Local-only storage | Deleted and edited history never leaves the device |
| iCloud backup | Account data is included in device backups |
---
## Localization
| Language | Coverage |
| :-- | :-- |
| English | All WinterGram settings strings |
| Russian | Full WinterGram UI via bundled seed translations |
---
## Channels and Links
| Resource | URL |
| :-- | :-- |
| Channel | https://t.me/wntgram |
| Beta | https://t.me/wntbeta |
| GitHub | https://github.com/reekeer/WinterGram |

View file

@ -1,86 +1,111 @@
# WinterGram — feature → implementation map
This document maps every WinterGram option to where it is (or needs to be) wired into the
Telegram-iOS codebase. The settings themselves live in a single store:
> Developer reference. User-facing overview: [`docs/FEATURES.md`](FEATURES.md).
This document maps every WinterGram option to where it is wired into the
codebase. The settings themselves live in a single store:
- `submodules/TelegramUIPreferences/Sources/WinterGramSettings.swift` — the `WinterGramSettings`
`Codable` struct, its sub-structs (`WinterGramLiquidGlass`) and enums, plus
`updateWinterGramSettingsInteractively(...)` and `winterGramSettings(accountManager:)`.
`updateWinterGramSettingsInteractively(...)`, `winterGramSettings(accountManager:)`, and the
synchronous snapshot `currentWinterGramSettings` (kept fresh by
`observeWinterGramSettings(accountManager:)`, started from `AppDelegate`).
- `submodules/TelegramUIPreferences/Sources/PostboxKeys.swift` — the shared-data key
`ApplicationSpecificSharedDataKeys.winterGramSettings` (value `23`).
- `submodules/TelegramCore/Sources/WinterGram/WinterGramCoreSettings.swift` — a minimal
mirror for hooks inside `TelegramCore` (which cannot import `TelegramUIPreferences`);
fed by the same observer.
Read the store anywhere that already has an `AccountContext` / `AccountManager` via
`winterGramSettings(accountManager:)`, and write it via `updateWinterGramSettingsInteractively`.
`winterGramSettings(accountManager:)` (reactive) or `currentWinterGramSettings` (sync),
and write it via `updateWinterGramSettingsInteractively`.
## Status legend
- **Store** — the setting exists and persists (done in this repo).
- **Hook** — the place in the app where behavior must read the setting.
- ✅ — setting persists and the behavior hook is in place.
- ⏳ — setting persists, behavior not fully hooked yet.
## Privacy & Ghost Mode
| Setting | Hook |
| :-- | :-- |
| `ghostModeEnabled` | Master switch read by the read-receipt / online-status / typing senders below. |
| `sendReadReceipts` | `TelegramCore` history read — gate `_internal_applyMaxReadIndex` / outgoing `messages.readHistory` calls. |
| `sendReadStories` | Story view reporting — gate `markStoryAsSeen` network calls. |
| `sendOnlineStatus` | Online presence — gate `account.updatePresence` / `updateStatus`. |
| `sendUploadProgress` | Typing/upload activity — gate `ChatActivity` / `setTyping` reporting. |
| `sendOfflineAfterOnline` | Emit a one-shot offline presence packet after the app goes online. |
| `markReadAfterAction` | After replying/reacting, locally mark the chat read without sending receipts. |
| `useScheduledMessages` | "Отложка" — when ghosting, send via the scheduled-messages path. |
| `sendWithoutSound` | Outgoing message flags — set `silent` per `shouldSendWithoutSound`. |
| `suggestGhostBeforeStory` | Story viewer — present the ghost confirmation before opening. |
| Setting | Status | Hook |
| :-- | :-- | :-- |
| `ghostModeEnabled` | ✅ | Master switch read by the gates below. |
| `sendReadReceipts` | ✅ | `AccountContext.applyMaxReadIndex` (`submodules/TelegramUI/Sources/AccountContext.swift`) returns early in ghost mode. |
| `sendReadStories` | ✅ | All four `markAsSeen` implementations in `StoryChatContent.swift` return early in ghost mode. |
| `sendOnlineStatus` | ✅ | `SharedWakeupManager` forces `shouldKeepOnlinePresence` to false in ghost mode. |
| `sendUploadProgress` | ✅ | Typing-activity subscription in `ChatController.swift` skips `updateLocalInputActivity` in ghost mode. |
| `sendOfflineAfterOnline` | ⏳ | Emit a one-shot offline presence packet after going online. |
| `markReadAfterAction` | ⏳ | After replying/reacting, locally mark read without sending receipts. |
| `useScheduledMessages` | ✅ | "Отложка": `transformEnqueueMessages` in `ChatController.swift` routes ghost-mode sends through the scheduled path (now + 12 s) so sending doesn't mark the chat read. |
| `sendWithoutSound` | ✅ | `transformEnqueueMessages` computes `effectiveSilentPosting` from never / in-ghost / always. |
| `suggestGhostBeforeStory` | ⏳ | Story viewer — present the ghost confirmation before opening. |
## History & Recovery
| Setting | Hook |
| :-- | :-- |
| `saveDeletedMessages` | Hook the deletion path in `Postbox` history removal; mirror messages into a local store before they are purged. |
| `saveMessagesHistory` | On `EditMessage` updates, append the previous version to a local edit-history store. |
| `semiTransparentDeletedMessages` | `ChatMessageItemView` — render saved-deleted bubbles at reduced alpha. |
| `deletedMark` / `editedMark` | Message footer rendering in the bubble content nodes. |
| Setting | Status | Hook |
| :-- | :-- | :-- |
| `saveDeletedMessages` | ✅ | Remote deletions (`.DeleteMessages` / `.DeleteMessagesWithGlobalIds`) skipped in `AccountStateManagementUtils.swift` via `currentWinterGramCoreSettings`. |
| `saveMessagesHistory` | ✅ | On remote `.EditMessage`, the previous text/entities/timestamp are appended to `WinterGramEditHistoryAttribute` (`submodules/TelegramCore/Sources/WinterGram/`); registered in `AccountManager.swift`. Viewing UI: ⏳. |
| `semiTransparentDeletedMessages` | ⏳ | Render kept-deleted bubbles at reduced alpha. |
## Hidden Archive ("AАrchive")
## Hidden Archive ("ААрхив")
| Setting | Hook |
| :-- | :-- |
| `stashedPeerIds` | Filter the chat list to hide these peers from the main list; show them only in the dedicated WinterGram archive screen. |
| `stashMuteNotifications` | Notification service extension — suppress notifications for stashed peers. |
| `stashAutoMarkRead` | On receiving from a stashed peer, locally mark read (respecting Ghost Mode). |
| Setting | Status | Hook |
| :-- | :-- | :-- |
| `stashedPeerIds` | ✅ | Hidden from the main tab in `ChatListNodeEntries.swift`; stash/unstash via chat-list context menu (`ChatContextMenus.swift`); browse via Settings → WinterGram → Stashed Chats (`WinterGramStashController.swift`). |
| `stashMuteNotifications` | ✅ | In-app notification pipeline in `ApplicationContext.swift` drops banners for stashed peers. (APNs pushes need the NotificationService extension: ⏳.) |
| `stashAutoMarkRead` | ✅ | Same pipeline calls `applyMaxReadIndexInteractively` for stashed peers. |
## Anti-Features
| Setting | Hook |
| :-- | :-- |
| `disableAds` | Sponsored-messages fetch in `TelegramCore` (`getAdMessages`) — return empty when disabled. |
| `localPremium` | `isPremium` resolution in the UI layer — treat as Premium locally for gated UI. |
| `shadowBanIds` | Chat history filtering — drop incoming messages from these peers from the rendered list. |
| `disableStories` | Story list assembly — hide the stories strip. |
| `hidePremiumStatuses` | Peer title rendering — drop Premium/emoji-status badges. |
| `disableOpenLinkWarning` | URL open path — skip the "open this link?" confirmation. |
| Setting | Status | Hook |
| :-- | :-- | :-- |
| `disableAds` | ✅ | Ad insertion gate in `ChatHistoryEntriesForView.swift`. |
| `localPremium` | ✅ | `isPremium` resolution in `submodules/TelegramUI/Sources/AccountContext.swift`. |
| `shadowBanIds` | ✅ | Entry filter by author in `ChatHistoryEntriesForView.swift`. |
| `disableStories` | ✅ | `shouldDisplayStoriesInChatListHeader` in `ChatListControllerNode.swift` returns false. |
| `hidePremiumStatuses` | ✅ | `ChatTitleView` / `ChatTitleComponent` / `ChatListItem` / `ItemListPeerItem`. |
| `disableOpenLinkWarning` | ✅ | Concealed-link alert gate in `OpenUserGeneratedUrl.swift`. |
## In-app purchases
Fully disabled: `InAppPurchaseManager.buyProduct` fails immediately with `.cantMakePayments`
(every purchase screen maps that to a localized error), and `PremiumIntroScreen.buy()` shows a
"subscribe via the official Telegram app" alert before reaching the manager. Redeeming gift
codes still works (not an IAP).
## Chat Conveniences
| Setting | Hook |
| :-- | :-- |
| `stickerConfirmation` / `gifConfirmation` / `voiceConfirmation` | Send paths in the chat input panel — present a confirm alert before sending. |
| `showMessageSeconds` | Timestamp formatting in the bubble footer. |
| `showPeerId` | Peer info / chat title — append the ID in Telegram or Bot API form. |
| `translateMessages` / `translationProvider` | Message context menu translate action + provider selection. |
| `webviewSpoofPlatform` / `increaseWebviewHeight` | WebApp controller — set the spoofed `tg_platform` / viewport height. |
| Setting | Status | Hook |
| :-- | :-- | :-- |
| `stickerConfirmation` / `gifConfirmation` | ✅ | `ChatController.swift` send paths. |
| `voiceConfirmation` | ✅ | `ChatControllerMediaRecording.swift`. |
| `showMessageSeconds` | ✅ | `StringForMessageTimestampStatus.swift`. |
| `showPeerId` | ✅ | ID row (long-press copies) in `PeerInfoProfileItems.swift` for users and channels/groups, honoring Telegram/Bot-API format. |
| `translateMessages` / `translationProvider` | ⏳ | Message context-menu translate + provider. |
| `webviewSpoofPlatform` | ✅ | `BotWebView.swift` in TelegramCore reads `currentWinterGramCoreSettings.webviewPlatform` (ios / android / macos / tdesktop), fed by the settings observer. |
| `increaseWebviewHeight` | ⏳ | WebApp controller viewport. |
## Appearance & Customization
| Setting | Hook |
| :-- | :-- |
| `liquidGlass.*` | `Display` blur/material layers behind the chat list, nav bar, and tab bar; read `enabled`, `transparency`, `blurRadius`, `tintColor`, per-surface flags. |
| `materialDesign` | Switch/control styling in `ItemListUI` components. |
| `avatarCornerRadius` / `singleCornerRadius` | Avatar node corner rounding in `AvatarNode`. |
| `messageBubbleRadius` / `removeMessageTail` | Bubble background drawing in the chat message backgrounds. |
| `customFont` / `monoFont` | `PresentationData` font resolution. |
| `appIcon` / `iconPack` | Alternate-icon switching via `UIApplication.setAlternateIconName`; see `Telegram/Telegram-iOS/DefaultAppIcon.xcassets/WinterGramDarkIcon.appiconset`. |
| `showOnlyAddedEmojisAndStickers` | Emoji/sticker panel data sources — filter to installed packs. |
| Setting | Status | Hook |
| :-- | :-- | :-- |
| `liquidGlass.*` | ⏳ | Blur/material layers behind chat list, nav bar, tab bar. |
| `materialDesign` | ⏳ | Switch/control styling. |
| `avatarCornerRadius` / `singleCornerRadius` | ⏳ | `AvatarNode` corner rounding. Note: photos are circle-clipped inside the bitmap (`PeerAvatar.swift` `roundCorners` mask), so a real implementation must touch every render path, not just `imageNode.cornerRadius`. |
| `messageBubbleRadius` / `removeMessageTail` | ⏳ | Bubble background drawing. |
| `customFont` / `monoFont` | ⏳ | `PresentationData` font resolution. |
| `appIcon` / `iconPack` | ⏳ | Alternate-icon switching; assets in `DefaultAppIcon.xcassets/WinterGramDarkIcon.appiconset`. |
| `showOnlyAddedEmojisAndStickers` | ⏳ | Emoji/sticker panel data sources. |
## Accounts
- Unlimited accounts: `maximumNumberOfAccounts` / `maximumPremiumNumberOfAccounts` = 100 in
`submodules/AccountUtils/Sources/AccountUtils.swift`; the add-account flow in
`PeerInfoScreenSettingsActions.swift` uses the same constants.
- Account data is included in iCloud/iTunes device backups (`isExcludedFromBackup = false` in
`TelegramCore/Sources/Account/AccountManager.swift`). Tradeoff: session auth keys become part
of the backup.
## Deep links — `wnt://`
@ -90,7 +115,7 @@ Normalized to `tg://` at the app entry by `normalizeWinterGramUrlScheme(_:)` in
## Settings UI
A dedicated **WinterGram** entry should be added to the settings list
(`submodules/SettingsUI` / the PeerInfo settings screen) that opens an `ItemListController`
backed by `winterGramSettings(accountManager:)` and writing through
`updateWinterGramSettingsInteractively`. Group the rows by the sections above.
The **WinterGram** entry is the first row of Settings (snowball icon,
`PresentationResourcesSettings.winterGram`), opening
`submodules/SettingsUI/Sources/WinterGramSettingsController.swift`. The Hidden Archive browser
lives in `WinterGramStashController.swift` next to it.

176
docs/wintergram-setup.md Normal file
View file

@ -0,0 +1,176 @@
# WinterGram Build & Install Guide
This guide covers how to build WinterGram from source, run it in the iOS Simulator, install it on a physical iPhone without a paid Apple Developer Program, and sideload the resulting IPA.
## Requirements
- macOS with Xcode (the project is tested on Xcode 26.x)
- Python 3
- ~60 GB of free disk space (Bazel cache + build artifacts)
- A free Apple ID (for on-device installation)
## 1. Clone the repository
```sh
git clone --recursive https://github.com/reekeer/WinterGram.git
cd WinterGram
```
If you cloned without `--recursive`:
```sh
git submodule update --init --recursive
```
## 2. Configure build credentials
WinterGram needs a development configuration with your own credentials.
1. Copy the example file:
```sh
cp build-system/wintergram-development-configuration.example.json \
build-system/wintergram-development-configuration.json
```
2. Edit `build-system/wintergram-development-configuration.json`:
- `bundle_id` — a unique bundle identifier (e.g. `dev.you.wintergram`).
- `api_id` / `api_hash` — obtain from <https://my.telegram.org/apps>.
- `team_id` — your Apple Developer Team ID (a 10-character string). For a free Apple ID, create an Apple Development certificate in Xcode → Settings → Accounts, then find the Team ID in Keychain Access under the certificate's **Organizational Unit** field.
- `app_specific_url_scheme` is `wnt` by default.
> **Important:** `api_id` and `api_hash` are baked into the app at build time. You cannot change them after installation because your Telegram session is tied to the `api_id` used when logging in.
The committed example uses public test values (`api_id: 2040`). Replace them with your own before installing on a personal device.
## 3. Build an unsigned IPA
### Convenience wrapper (recommended)
`scripts/build-wintergram.sh` wraps the Bazel/Make.py invocations and emits WinterGram-named IPAs in `build/`:
```sh
./scripts/build-wintergram.sh sim # simulator -> build/WinterGram-Simulator.ipa
./scripts/build-wintergram.sh --install # build sim + install into the booted Simulator (sim only)
./scripts/build-wintergram.sh --install --run # also launch it
./scripts/build-wintergram.sh sideload # device IPA -> build/WinterGram.ipa
./scripts/build-wintergram.sh livecontainer # unsigned IPA -> build/WinterGram-LiveContainer.ipa
./scripts/build-wintergram.sh all # all of the above
```
`--clean` wipes `build/` first; `--open-build-dir` reveals the output in Finder; `--help` lists everything.
The raw commands below are equivalent if you prefer to invoke Make.py directly.
The fastest path to a device IPA is a terminal build:
```sh
python3 build-system/Make/Make.py --overrideXcodeVersion \
--cacheDir ~/telegram-bazel-cache \
build \
--configurationPath build-system/wintergram-development-configuration.json \
--xcodeManagedCodesigning \
--buildNumber=1 \
--configuration=debug_arm64
```
The finished `.ipa` is written to `bazel-bin/Telegram/`.
For a simulator build, use `debug_sim_arm64` and fake codesigning files (the Simulator does not validate signatures, but Bazel still requires provisioning profiles):
```sh
python3 build-system/Make/Make.py --overrideXcodeVersion \
--cacheDir ~/telegram-bazel-cache \
build \
--configurationPath build-system/wintergram-development-configuration.json \
--codesigningInformationPath build-system/fake-codesigning \
--buildNumber=1 \
--configuration=debug_sim_arm64
```
Add `--continueOnError` after `build` to see every error in one pass. `--xcodeManagedCodesigning` is intended for `generateProject`; for terminal builds with extensions, use `--codesigningInformationPath build-system/fake-codesigning`.
## 4. Install on a physical iPhone (free Apple ID)
### With AltStore / SideStore
1. Build a device IPA (`debug_arm64` or `release_arm64`) with your Team ID in the config.
2. Install AltStore on your phone (via AltServer on your Mac).
3. In AltStore, tap **+**, select the built `.ipa`, and let AltStore re-sign and install it.
4. A free Apple ID must re-sign the app every 7 days. AltStore and SideStore can refresh this automatically in the background.
### With Xcode directly
1. Generate the Xcode project (see below).
2. Select the **Telegram** scheme and your connected iPhone as the destination.
3. In the target's **Signing & Capabilities**, choose your Personal Team.
4. Press ⌘R. The first time, trust the developer on the device in Settings → General → VPN & Device Management.
Free provisioning profiles last 7 days and are limited to 3 apps per Apple ID. Push notifications via APNs, Siri, and iCloud are unavailable with a free account, so the dev config has `enable_siri` and `enable_icloud` disabled.
## 5. Generate an Xcode project
```sh
python3 build-system/Make/Make.py --overrideXcodeVersion \
--cacheDir ~/telegram-bazel-cache \
generateProject \
--configurationPath build-system/wintergram-development-configuration.json \
--xcodeManagedCodesigning
```
To generate a simulator-only project without provisioning profiles, add `--disableProvisioningProfiles`.
The command opens the generated `Telegram.xcodeproj` automatically. If it does not, the project is in the repository root.
## 6. Run in the Simulator
1. Open the generated `Telegram.xcodeproj`.
2. Select the **Telegram** scheme and any iPhone Simulator.
3. Press ⌘R.
The first build is slow (4090 minutes) because WebRTC and ffmpeg are compiled from source. Incremental builds are much faster. If Xcode hangs on "build-request.json not updated yet", cancel and run again; this is a known rules_xcodeproj quirk.
## App icons
All WinterGram app-icon assets are generated from source PNGs in `branding/` by a single script:
```sh
./scripts/generate-app-icons.sh
```
Each source named `branding/wnt-app-icon-<variant>.png` (square, ≥1024×1024) becomes an alternate
icon `WinterGram<Variant>`. The two shipping icons are `wnt-app-icon-dark.png` (also the primary
home-screen icon) and `wnt-app-icon-light.png`. The script regenerates the `.alticon` folders, the
primary `WinterGramDarkIcon.appiconset`, and the in-app preview imagesets in one pass.
A brand-new variant is generated but must still be registered manually — the script prints the three
spots: `Telegram/BUILD` (`alternate_icon_folders`), `AppDelegate.getAvailableAlternateIcons()`, and the
preview-imageset mapping in `applicationBindings`.
## Repository layout
```
WinterGram/
├── Telegram/ ← App entry point and extensions
│ ├── Telegram-iOS/ ← Info.plist, assets, xcconfig
│ ├── Share / SiriIntents / NotificationService / ...
│ └── WatchApp/ ← watchOS client snapshot
├── submodules/ ← Functionality split into Bazel modules
│ ├── TelegramUIPreferences/Sources/WinterGramSettings.swift
│ └── SettingsUI/Sources/WinterGramSettingsController.swift
├── branding/ ← Source art: app-icon PNGs (wnt-app-icon-*.png) + badge/snowflake shapes
├── scripts/ ← Build + tooling
│ ├── build-wintergram.sh ← Convenience build wrapper (sim / sideload / livecontainer)
│ └── generate-app-icons.sh ← App-icon generator (reads branding/wnt-app-icon-*.png)
├── docs/ ← Documentation
├── build-system/ ← Bazel wrapper and configs
│ └── wintergram-development-configuration.json ← your dev config
├── third-party/ ← External dependencies
└── Tests/ ← Bazel test app targets
```
## Troubleshooting
- **"No .mobileprovision targets for extensions"** when using `--xcodeManagedCodesigning` in a terminal build: use `--codesigningInformationPath build-system/fake-codesigning` instead, or use `generateProject` for Xcode-managed signing.
- **Build fails on the first try:** ensure submodules are fully initialized and you have enough disk space.
- **App crashes on launch after re-signing:** make sure the `bundle_id` in your config is unique and the `team_id` matches the certificate used to sign.