diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..3db8eb743d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,373 @@ +# Claude Code Guide for Telegram Desktop + +This guide explains how AI assistants should work with the Telegram Desktop codebase. + +## Build System Structure + +The build system expects this directory layout: + +``` +D:\Telegram\ # BuildPath +├── tdesktop/ # Repository (you work here) +├── Libraries/ # 32-bit dependencies (Linux/macOS) +├── win64/ +│ └── Libraries/ # 64-bit dependencies (Windows) +└── ThirdParty/ # Build tools (NuGet, Python, etc.) +``` + +Dependencies are located relative to the repository: `../Libraries`, `../win64/Libraries`, or `../ThirdParty`. + +## Build Configuration + +### Build Commands + +**From repository root, run:** + +```bash +cmake --build out --config Debug --target Telegram +``` + +That's it. The `out/` directory is already configured. The executable will be at `out/Debug/Telegram.exe`. + +**Important:** When running cmake from a shell that doesn't support `cd`, use quoted absolute paths: +```bash +cmake --build "d:\Telegram\tx64\out" --config Debug --target Telegram +``` + +**Never build Release** - it's extremely heavy and not needed for testing changes. + +## Platform-Specific Requirements + +### Windows +- Requires Visual Studio 2022 +- Must run from appropriate Native Tools Command Prompt: + - "x64 Native Tools Command Prompt" for `win64` + - "x86 Native Tools Command Prompt" for `win` + - "ARM64 Native Tools Command Prompt" for `winarm` +- Dependencies: `../win64/Libraries` (64-bit) or `../Libraries` (32-bit) + +### macOS +- Requires Xcode +- Dependencies: `../Libraries/local/Qt-*` +- Set `QT` environment variable: `export QT=6.8` + +### Linux +- Build dependencies in `../Libraries` +- Set `QT` environment variable if needed + +## Key Files + +- **`Telegram/build/version`** - Version information +- **`out/`** - Build output directory + +## Troubleshooting + +### "Libraries not found" +Ensure the repository is in `D:\Telegram\tdesktop`. The build system requires `../win64/Libraries` to exist. + +### Build fails with "wrong command prompt" +On Windows, use the correct Visual Studio Native Tools Command Prompt matching your target (x64/x86/ARM64). + +### Build fails with PDB or EXE access errors +If the build fails with errors like: +- `cannot open program database '...vc143.pdb'` +- `cannot open output file 'Telegram.exe'` + +**Do NOT retry the build repeatedly.** First check if `out/Debug/Telegram.exe` can be deleted: +```bash +rm out/Debug/Telegram.exe +``` +If deletion fails, the user is likely running Telegram.exe for testing. Inform the user: +> "Build failed due to file lock. Please close Telegram.exe so I can complete the build." + +Do not attempt to fix this automatically - wait for user confirmation. + +## Best Practices + +1. **Always use Debug builds** - Release builds are extremely heavy +2. **Don't build Release configuration** - it's too heavy for testing + +--- + +# Development Guidelines + +## Coding Style + +**Do NOT write comments in code:** + +This is important! Do not write single-line comments that describe what the next line does - they are bloat. Comments are allowed ONLY to describe complex algorithms in detail, when the explanation requires at least 4-5 lines. Self-documenting code with clear variable and function names is preferred. + +```cpp +// BAD - don't do this: +// Get the user's name +auto name = user->name(); +// Check if premium +if (user->isPremium()) { + +// GOOD - no comments needed, code is self-explanatory: +auto name = user->name(); +if (user->isPremium()) { + +// ACCEPTABLE - complex algorithm explanation (4+ lines): +// The algorithm works by first collecting all visible messages +// in the viewport, then calculating their intersection with +// the clip rectangle. Messages are grouped by date headers, +// and we need to account for sticky headers that may overlap +// with the first message in each group. +``` + +**Empty line before closing brace:** + +Always add an empty line before the closing brace of a class (after all private fields): + +```cpp +// GOOD: +class MyClass { +public: + void foo(); + +private: + int _value = 0; + +}; + +// BAD: +class MyClass { +public: + void foo(); + +private: + int _value = 0; +}; +``` + +**Use `auto` for type deduction:** + +Prefer `auto` (or `const auto`, `const auto &`) instead of explicit types: + +```cpp +// Prefer this: +auto currentTitle = tr::lng_settings_title(tr::now); +auto nameProducer = GetNameProducer(); + +// Instead of this: +QString currentTitle = tr::lng_settings_title(tr::now); +rpl::producer nameProducer = GetNameProducer(); +``` + +## API Usage + +### API Schema Files + +API definitions use [TL Language](https://core.telegram.org/mtproto/TL): + +1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`** - MTProto protocol (encryption, auth, etc.) +2. **`Telegram/SourceFiles/mtproto/scheme/api.tl`** - Telegram API (messages, users, chats, etc.) + +### Making API Requests + +Standard pattern using `api()`, generated `MTP...` types, and callbacks: + +```cpp +api().request(MTPnamespace_MethodName( + MTP_flags(flags_value), + MTP_inputPeer(peer), + MTP_string(messageText), + MTP_long(randomId), + MTP_vector() +)).done([=](const MTPResponseType &result) { + // Handle successful response + + // Multiple constructors - use .match() or check type: + result.match([&](const MTPDuser &data) { + // use data.vfirst_name().v + }, [&](const MTPDuserEmpty &data) { + // handle empty user + }); + + // Single constructor - use .data() shortcut: + const auto &data = result.data(); + // use data.vmessages().v + +}).fail([=](const MTP::Error &error) { + // Handle API error + if (error.type() == u"FLOOD_WAIT_X"_q) { + // Handle flood wait + } +}).handleFloodErrors().send(); +``` + +**Key points:** +- Always refer to `api.tl` for method signatures and return types +- Use generated `MTP...` types for parameters (`MTP_int`, `MTP_string`, etc.) +- For multiple constructors, use `.match()` or check `.type()` then `.c_constructor()` +- For single constructors, use `.data()` shortcut +- Include `.handleFloodErrors()` before `.send()` + +## UI Styling + +### Style Files + +UI styles are defined in `.style` files using custom syntax: + +```style +using "ui/basic.style"; +using "ui/widgets/widgets.style"; + +MyButtonStyle { + textPadding: margins; + icon: icon; + height: pixels; +} + +defaultButton: MyButtonStyle { + textPadding: margins(10px, 15px, 10px, 15px); + icon: icon{{ "gui/icons/search", iconColor }}; + height: 30px; +} + +primaryButton: MyButtonStyle(defaultButton) { + icon: icon{{ "gui/icons/check", iconColor }}; +} +``` + +**Built-in types:** +- `int` - Integer numbers +- `pixels` - Pixel values with `px` suffix (e.g., `10px`) +- `color` - Named colors from `ui/colors.palette` +- `icon` - Inline icon definition: `icon{{ "path/stem", color }}` +- `margins` - Four values: `margins(top, right, bottom, left)` +- `size` - Two values: `size(width, height)` +- `point` - Two values: `point(x, y)` +- `align` - Alignment: `align(center)`, `align(left)` +- `font` - Font: `font(14px semibold)` +- `double` - Floating point + +### Usage in Code + +```cpp +#include "styles/style_widgets.h" + +// Access style members +int height = st::primaryButton.height; +const style::icon &icon = st::primaryButton.icon; +style::margins padding = st::primaryButton.textPadding; + +// Use in painting +void MyWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + p.fillRect(rect(), st::chatInput.backgroundColor); +} +``` + +## Localization + +### String Definitions + +Strings are defined in `Telegram/Resources/langs/lang.strings`: + +``` +"lng_settings_title" = "Settings"; +"lng_confirm_delete_item" = "Are you sure you want to delete {item_name}?"; +"lng_files_selected#one" = "{count} file selected"; +"lng_files_selected#other" = "{count} files selected"; +``` + +### Usage in Code + +**Immediate (current value):** + +```cpp +auto currentTitle = tr::lng_settings_title(tr::now); + +auto currentConfirmation = tr::lng_confirm_delete_item( + tr::now, + lt_item_name, currentItemName); + +auto filesText = tr::lng_files_selected(tr::now, lt_count, count); +``` + +**Reactive (rpl::producer):** + +```cpp +auto titleProducer = tr::lng_settings_title(); + +auto confirmationProducer = tr::lng_confirm_delete_item( + lt_item_name, + std::move(itemNameProducer)); + +auto filesTextProducer = tr::lng_files_selected( + lt_count, + countProducer | tr::to_count()); +``` + +**Key points:** +- Pass `tr::now` as first argument for immediate `QString` +- Omit `tr::now` for reactive `rpl::producer` +- Placeholders use `lt_tag_name, value` pattern +- For `{count}`: immediate uses `int`, reactive uses `rpl::producer` with `| tr::to_count()` +- Move producers with `std::move` when passing to placeholders + +## RPL (Reactive Programming Library) + +### Core Concepts + +**Producers** represent streams of values over time: + +```cpp +auto intProducer = rpl::single(123); // Emits single value +auto lifetime = rpl::lifetime(); // Manages subscription lifetime +``` + +### Starting Pipelines + +```cpp +std::move(counter) | rpl::on_next([=](int value) { + qDebug() << "Received: " << value; +}, lifetime); + +// Without lifetime parameter - MUST store returned lifetime: +auto subscriptionLifetime = std::move(counter) | rpl::on_next([=](int value) { + // process value +}); +``` + +### Transforming Producers + +```cpp +auto strings = std::move(ints) | rpl::map([](int value) { + return QString::number(value * 2); +}); + +auto evenInts = std::move(ints) | rpl::filter([](int value) { + return (value % 2 == 0); +}); +``` + +### Combining Producers + +**`rpl::combine`** - combines latest values (lambdas receive unpacked arguments): + +```cpp +auto combined = rpl::combine(countProducer, textProducer); + +std::move(combined) | rpl::on_next([=](int count, const QString &text) { + qDebug() << "Count=" << count << ", Text=" << text; +}, lifetime); +``` + +**`rpl::merge`** - merges producers of same type: + +```cpp +auto merged = rpl::merge(sourceA, sourceB); + +std::move(merged) | rpl::on_next([=](QString &&value) { + qDebug() << "Merged value: " << value; +}, lifetime); +``` + +**Key points:** +- Explicitly `std::move` producers when starting pipelines +- Pass `rpl::lifetime` to `on_...` methods or store returned lifetime +- Use `rpl::duplicate(producer)` to reuse a producer multiple times +- Combined producers automatically unpack tuples in lambdas