mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
[cc] Add CLAUDE.md for the Claude Code agent.
This commit is contained in:
@@ -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<QString> 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<MTPMessageEntity>()
|
||||||
|
)).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<QString>`
|
||||||
|
- Placeholders use `lt_tag_name, value` pattern
|
||||||
|
- For `{count}`: immediate uses `int`, reactive uses `rpl::producer<float64>` 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
|
||||||
Reference in New Issue
Block a user