Skip to content

Commit f838f51

Browse files
isaacclaude
andcommitted
feat: composer custom-emoji / mention / date round-trip + TextFormat ios_unit_test infra
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4302a26 commit f838f51

10 files changed

Lines changed: 305 additions & 498 deletions

File tree

‎CLAUDE.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ Add `--continueOnError` after `build` (forwards to bazel's `--keep_going`) when
2121

2222
The build needs `TELEGRAM_CODESIGNING_GIT_PASSWORD` in the environment. It is set in `~/.zshrc` but Claude Code's bash tool does NOT source shell config by default. Prefix build commands with `source ~/.zshrc 2>/dev/null;` to pick it up.
2323

24+
**Running tests.** `Make.py test` runs Bazel test targets (same config + codesigning as `build`, forced `debug_sim_arm64`). It accepts `--target <label>` (added 2026-06-19; default `Tests/AllTests`) so a single `ios_unit_test` can run in isolation, e.g.:
25+
26+
```sh
27+
source ~/.zshrc 2>/dev/null; python3 build-system/Make/Make.py --overrideXcodeVersion --cacheDir ~/telegram-bazel-cache \
28+
test --configurationPath build-system/appstore-configuration.json \
29+
--gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git \
30+
--gitCodesigningType development --gitCodesigningUseCurrent --target //submodules/TextFormat:TextFormatTests
31+
```
32+
33+
The first app-side `ios_unit_test` is `//submodules/TextFormat:TextFormatTests` (the mention/date link codecs). An `ios_unit_test` here needs an `ios_test_runner` pinned to a real device/OS (e.g. `iPhone 17` / `26.5`) — the default runner picks an invalid device and the test process exits 15. **Run new targets via `--target`, not the default suite:** `Tests/AllTests` currently references a dangling `//submodules/TgVoipWebrtc:TgCallsTests`, so the default would fail to build until that suite is repaired.
34+
2435
## Code Style Guidelines
2536
- **Naming**: PascalCase for types, camelCase for variables/methods
2637
- **Imports**: Group and sort imports at the top of files
@@ -33,7 +44,7 @@ The build needs `TELEGRAM_CODESIGNING_GIT_PASSWORD` in the environment. It is se
3344
- Core launch and application extensions code is in `Telegram/` directory
3445
- Most code is organized into libraries in `submodules/`
3546
- External code is located in `third-party/`
36-
- No tests are used at the moment
47+
- App-side unit tests are minimal: the first `ios_unit_test` (`//submodules/TextFormat:TextFormatTests`) was added 2026-06-19 (run via `Make.py test --target` — see Build). The RichTextEditor SwiftPM package keeps its own suite (`swift test` / `Scripts/iostest.sh`). Most modules still have no tests.
3748

3849
## RichTextEditor editor (`submodules/TelegramUI/Components/RichTextEditor`)
3950

@@ -45,6 +56,8 @@ Both Bazel targets — `//submodules/TelegramUI/Components/RichTextEditor:RichTe
4556

4657
**Chat composer text context menu (added 2026-06-17, runtime-verified on iOS 26 sim).** The new editor backend now surfaces the composer's **Format** submenu (Bold/Italic/Monospace/Link/Strikethrough/Underline/Quote/Spoiler/Date/Code, secret-chat gated) in its iOS-16 edit menu, via a new `RichTextEditorView.contextMenuItemsProvider` hook that `ChatTextInputPanelNode` installs (it transforms the editor's default menu elements — drops the editor's built-in 3-item Format submenu, splices in the richer one, preserves Look Up/Translate/Share). Actions route to the editor's **native engine** (`toggle*`/`setParagraphStyle(.quote)`); **Link** is wired through the host `openLinkEditing` (branched in `ChatControllerLoadDisplayNode` for the native backend, using the editor's `currentLink()`/`selectedText()`/`setLink`/`removeLink`); **Date/Code are deferred no-ops** (editor lacks a code-block style / timestamp entity). The detailed seam (`ChatRichTextInputNode.contextMenuItemsProvider` / `performFormatAction` / `currentRichTextLinkURL` / `selectedRichText` / `applyRichTextLink` / `usesNativeRichTextEngine`) and the **load-bearing `Display.Window1.hitTest` "EditMenu" match** (without which the iOS-16 edit menu's taps fall through to content app-wide) are documented in the editor's own `CLAUDE.md`. iOS 13–15 keeps the editor's built-in menu (UIMenuController can't carry closure-backed items).
4758

59+
**Composer bridge round-trip — custom emoji / mention / date (added 2026-06-19, `feature/richtext-composer-bridge-gaps`).** The native-engine composer converts to/from the chat `NSAttributedString` currency via `ComposerDocumentBridge` (live) and `EntityMessageBuilder` (send). These now **losslessly round-trip** three previously-dropped inline features: **custom emoji** (`ChatTextInputAttributes.customEmoji` ↔ `EmojiRef`, one `U+FFFC`, original placeholder kept as `altText`), **text mentions**, and **dates** — the latter two encoded into the Document's shared `link` field via `tg://user?id=` / `tg://timestamp?t=` markers from the shared `TextFormat` codec (`MentionDateMarkers.swift`: `mentionMarkdownURL`/`dateMarkdownURL`/`classifyChatLink`/`chatInputLinkAttribute`, with unit tests in `//submodules/TextFormat:TextFormatTests`). The send path is automatic: the builder stamps the chat attributes and `generateChatInputTextEntities` already emits the matching entities. **Creation is unchanged** — the Format▸Date/Code menu items are still no-ops; this is preservation only. **Code blocks still degrade to body** (deferred to a separate spec — needs a `Block.code` variant). **Known accepted limitation:** a `textUrl` link whose string equals a `tg://` marker (via markdown/paste/edit, not the in-app editor) is reinterpreted as a mention/date on round-trip — low-probability, documented in `MentionDateMarkers.swift`. Spec/plan: `docs/superpowers/{specs/2026-06-18-richtext-composer-bridge-gaps-design.md,plans/2026-06-18-richtext-composer-bridge-gaps.md}`.
60+
4861
## Embedded watch app (`Telegram/WatchApp`)
4962

5063
A standalone watchOS Telegram client (developed in the separate `~/build/tgwatch` repo) is vendored into this repo at `Telegram/WatchApp/` and can be embedded into the **device** IPA under `Telegram.app/Watch/`. It is built by `xcodebuild` (not Bazel) and codesigned by the Bazel build.

‎build-system/Make/Make.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ def invoke_build(self):
322322
print(subprocess.list2cmdline(combined_arguments))
323323
call_executable(combined_arguments)
324324

325-
def invoke_test(self):
325+
def invoke_test(self, test_target='Tests/AllTests'):
326326
combined_arguments = [
327327
self.build_environment.bazel_path
328328
]
@@ -332,7 +332,7 @@ def invoke_test(self):
332332
combined_arguments += ['--cache_test_results=no']
333333
combined_arguments += ['--test_output=errors']
334334

335-
combined_arguments += ['Tests/AllTests']
335+
combined_arguments += [test_target]
336336

337337
combined_arguments += self.common_args
338338
combined_arguments += self.common_build_args
@@ -760,7 +760,7 @@ def test(bazel, arguments):
760760
bazel_command_line.set_configuration('debug_sim_arm64')
761761
bazel_command_line.set_build_number('10000')
762762

763-
bazel_command_line.invoke_test()
763+
bazel_command_line.invoke_test(test_target=arguments.target)
764764

765765

766766
def query(bazel, arguments):
@@ -960,6 +960,12 @@ def add_project_and_build_common_arguments(current_parser: argparse.ArgumentPars
960960
'''
961961
)
962962
add_project_and_build_common_arguments(testParser)
963+
testParser.add_argument(
964+
'--target',
965+
type=str,
966+
default='Tests/AllTests',
967+
help='Bazel test target to run (default: Tests/AllTests, the full suite).'
968+
)
963969

964970
generateProjectParser = subparsers.add_parser('generateProject', help='Generate Xcode project')
965971
generateProjectParser.add_argument(

0 commit comments

Comments
 (0)