[ai] Move codex skill to correct place.

This commit is contained in:
John Preston
2026-02-13 13:22:33 +04:00
parent 7201f8a86e
commit d98011ccdc
8 changed files with 504 additions and 900 deletions
+454
View File
@@ -0,0 +1,454 @@
# Phase Prompts
Use these templates for `codex exec --json` child runs. Replace `<TASK>`, `<PROJECT>`, `<LETTER>`, and `<REPO_ROOT>`.
## Phase 0: Setup
**Record the current time now** and store it as `$START_TIME`. You will use this at the end to display total elapsed time.
Before running any phase prompts, the orchestrator must determine whether this is a new project or a follow-up task.
**Follow-up detection (MANDATORY — do this BEFORE anything else):**
1. Extract the first word/token from the task description. Call it `FIRST_TOKEN`.
2. Run these two checks IN PARALLEL:
- `ls .ai/` — to see all existing project names
- `ls .ai/<FIRST_TOKEN>/about.md` — to check if this specific project exists
3. If check 2 **succeeds** (the file exists): this is a **follow-up task**. The project name is `FIRST_TOKEN`. The task description is everything after `FIRST_TOKEN`.
4. If check 2 **fails** (file not found): this is a **new project**. The full input is the task description.
**Do NOT proceed until you have run these checks and determined follow-up vs new.**
**For new projects:**
- Using the list from check 1, pick a unique short name (1-2 lowercase words, hyphen-separated) that doesn't collide with existing projects.
- Create `.ai/<PROJECT>/` and `.ai/<PROJECT>/a/` and `logs/`.
- Set `<LETTER>` = `a`.
**For follow-up tasks:**
- Scan `.ai/<PROJECT>/` for existing task folders (`a/`, `b/`, ...). Find the latest one (highest letter).
- The previous task letter = that highest letter.
- The new task letter = next letter in sequence.
- Create `.ai/<PROJECT>/<LETTER>/` and `logs/`.
Then proceed to Phase 1. Follow-up tasks do NOT skip context gathering — they go through a modified version of it.
## Phase 1: Context (New Project, letter = `a`)
```text
You are a context-gathering agent for a large C++ codebase (Telegram Desktop).
TASK: <TASK>
YOUR JOB: Read AGENTS.md, inspect the codebase, find ALL files and code relevant to this task, and write two documents.
Steps:
1. Read AGENTS.md for project conventions and build instructions.
2. Search the codebase for files, classes, functions, and patterns related to the task.
3. Read all potentially relevant files. Be thorough - read more rather than less.
4. For each relevant file, note:
- File path
- Relevant line ranges
- What the code does and how it relates to the task
- Key data structures, function signatures, patterns used
5. Look for similar existing features that could serve as a reference implementation.
6. Check api.tl if the task involves Telegram API.
7. Check .style files if the task involves UI.
8. Check lang.strings if the task involves user-visible text.
Write TWO files:
### File 1: .ai/<PROJECT>/about.md
NOTE: This file is NOT used by any agent in the current task. It exists solely as a starting point for a FUTURE follow-up task's context gatherer. No planning, implementation, or review agent will ever read it. Only the context-gathering agent of the next follow-up task reads about.md (together with the latest context.md) to produce a fresh context.md for that next task.
Write it as if the project is already fully implemented and working. It should contain:
- **Project**: What this project does (feature description, goals, scope)
- **Architecture**: High-level architectural decisions, which modules are involved, how they interact
- **Key Design Decisions**: Important choices made about the approach
- **Relevant Codebase Areas**: Which parts of the codebase this project touches, key types and APIs involved
Do NOT include temporal state like "Current State", "Pending Changes", "Not yet implemented", "TODO", or any other framing that distinguishes between "done" and "not done". Describe the project as a complete, coherent whole — as if everything is already working. This is a project overview, not a status tracker. Task-specific work belongs exclusively in context.md.
### File 2: .ai/<PROJECT>/a/context.md
This is the task-specific implementation context. This is the PRIMARY document — all downstream agents (planning, implementation, review) will read ONLY this file. It must be completely self-contained. It should contain:
- **Task Description**: The full task restated clearly
- **Relevant Files**: Every file path with line ranges and descriptions of what's there
- **Key Code Patterns**: How similar things are done in the codebase (with code snippets)
- **Data Structures**: Relevant types, structs, classes
- **API Methods**: Any TL schema methods involved (copied from api.tl)
- **UI Styles**: Any relevant style definitions
- **Localization**: Any relevant string keys
- **Build Info**: Build command and any special notes
- **Reference Implementations**: Similar features that can serve as templates
Be extremely thorough. Another agent with NO prior context will read this file and must be able to understand everything needed to implement the task.
Do not implement code in this phase.
```
## Phase 1F: Context (Follow-up Task, letter = `b`, `c`, ...)
```text
You are a context-gathering agent for a follow-up task on an existing project in a large C++ codebase (Telegram Desktop).
NEW TASK: <TASK>
YOUR JOB: Read the existing project state, gather any additional context needed, and produce fresh documents for the new task.
Steps:
1. Read AGENTS.md for project conventions and build instructions.
2. Read .ai/<PROJECT>/about.md — this is the project-level blueprint describing everything done so far.
3. Read .ai/<PROJECT>/<PREV_LETTER>/context.md — this is the previous task's gathered context.
4. Understand what has already been implemented by reading the actual source files referenced in about.md and the previous context.
5. Based on the NEW TASK description, search the codebase for any ADDITIONAL files, classes, functions, and patterns that are relevant to the new task but not already covered.
6. Read all newly relevant files thoroughly.
Write TWO files:
### File 1: .ai/<PROJECT>/about.md (REWRITE)
NOTE: This file is NOT used by any agent in the current task. It exists solely as a starting point for a FUTURE follow-up task's context gatherer. No planning, implementation, or review agent will ever read it. You are rewriting it now so that the next follow-up has an accurate project overview to start from.
REWRITE this file (not append). The new about.md must be a single coherent document that describes the project as if everything — including this new task's changes — is already fully implemented and working.
It should incorporate:
- Everything from the old about.md that is still accurate and relevant
- The new task's functionality described as part of the project (not as "changes to make")
- Any changed design decisions or architectural updates from the new task requirements
It should NOT contain:
- Any temporal state: "Current State", "Pending Changes", "TODO", "Not yet implemented"
- History of how requirements changed between tasks
- References to "the old approach" vs "the new approach"
- Task-by-task changelog or timeline
- Any distinction between "what was done before" and "what this task adds"
- Information that contradicts the new task requirements (if the new task changes direction, the about.md should reflect the NEW direction as if it was always the plan)
### File 2: .ai/<PROJECT>/<LETTER>/context.md
This is the PRIMARY document — all downstream agents (planning, implementation, review) will read ONLY this file. It must be completely self-contained. about.md will NOT be available to them.
It should contain:
- **Task Description**: The new task restated clearly, with enough project background (from about.md and previous context.md) that an implementation agent can understand it without reading any other .ai/ files
- **Relevant Files**: Every file path with line ranges relevant to THIS task (including files modified by previous tasks and any newly relevant files)
- **Key Code Patterns**: How similar things are done in the codebase
- **Data Structures**: Relevant types, structs, classes
- **API Methods**: Any TL schema methods involved
- **UI Styles**: Any relevant style definitions
- **Localization**: Any relevant string keys
- **Build Info**: Build command and any special notes
- **Reference Implementations**: Similar features that can serve as templates
Be extremely thorough. Another agent with NO prior context will read ONLY this file and must be able to understand everything needed to implement the new task. Do NOT assume the reader has seen about.md or any previous task files. The context.md is the single source of truth for all downstream agents — it must include all relevant project background, not just the delta.
Do not implement code in this phase.
```
## Phase 2: Plan
```text
You are a planning agent. You must create a detailed implementation plan.
Read these files:
- .ai/<PROJECT>/<LETTER>/context.md - Contains all gathered context for this task
- Then read the specific source files referenced in context.md to understand the code deeply.
Create a detailed plan in: .ai/<PROJECT>/<LETTER>/plan.md
The plan.md should contain:
## Task
<one-line summary>
## Approach
<high-level description of the implementation approach>
## Files to Modify
<list of files that will be created or modified>
## Files to Create
<list of new files, if any>
## Implementation Steps
Each step must be specific enough that an agent can execute it without ambiguity:
- Exact file paths
- Exact function names
- What code to add/modify/remove
- Where exactly in the file (after which function, in which class, etc.)
Number every step. Group steps into phases if there are more than ~8 steps.
### Phase 1: <name>
1. <specific step>
2. <specific step>
...
### Phase 2: <name> (if needed)
...
## Build Verification
- Build command to run
- Expected outcome
## Status
- [ ] Phase 1: <name>
- [ ] Phase 2: <name> (if applicable)
- [ ] Build verification
- [ ] Code review
Do not implement code in this phase.
```
## Phase 3: Plan Assessment
```text
You are a plan assessment agent. Review and refine an implementation plan.
Read these files:
- .ai/<PROJECT>/<LETTER>/context.md
- .ai/<PROJECT>/<LETTER>/plan.md
- Then read the actual source files referenced to verify the plan makes sense.
Assess the plan:
1. **Correctness**: Are the file paths and line references accurate? Does the plan reference real functions and types?
2. **Completeness**: Are there missing steps? Edge cases not handled?
3. **Code quality**: Will the plan minimize code duplication? Does it follow existing codebase patterns from AGENTS.md?
4. **Design**: Could the approach be improved? Are there better patterns already used in the codebase?
5. **Phase sizing**: Each phase should be implementable by a single agent in one session. If a phase has more than ~8-10 substantive code changes, split it further.
Update plan.md with your refinements. Keep the same structure but:
- Fix any inaccuracies
- Add missing steps
- Improve the approach if you found better patterns
- Ensure phases are properly sized for single-agent execution
- Add a line at the top of the Status section: `Phases: <N>` indicating how many implementation phases there are
- Add `Assessed: yes` at the bottom of the file
If the plan is small enough for a single agent (roughly <=8 steps), mark it as a single phase.
Do not implement code in this phase.
```
## Phase 4: Implementation
For each phase in the plan that is not yet marked as done, run a separate child session:
```text
You are an implementation agent working on phase <N> of an implementation plan.
Read these files first:
- .ai/<PROJECT>/<LETTER>/context.md - Full codebase context
- .ai/<PROJECT>/<LETTER>/plan.md - Implementation plan
Then read the source files you'll be modifying.
YOUR TASK: Implement ONLY Phase <N> from the plan:
<paste the specific phase steps here>
Rules:
- Follow the plan precisely
- Follow AGENTS.md coding conventions (no comments except complex algorithms, use auto, empty line before closing brace, etc.)
- Do NOT modify .ai/ files except to update the Status section in plan.md
- When done, update plan.md Status section: change `- [ ] Phase <N>: ...` to `- [x] Phase <N>: ...`
- Do NOT work on other phases
When finished, report what you did and any issues encountered.
```
After each implementation agent returns:
1. Read `plan.md` to check the status was updated.
2. If more phases remain, run the next implementation child session.
3. If all phases are done, proceed to build verification.
## Phase 5: Build Verification
Only run this phase if the task involved modifying project source code (not just docs or config).
```text
You are a build verification agent.
Read these files:
- .ai/<PROJECT>/<LETTER>/context.md
- .ai/<PROJECT>/<LETTER>/plan.md
The implementation is complete. Your job is to build the project and fix any build errors.
Steps:
1. Run (from repository root): cmake --build ./out --config Debug --target Telegram
2. If the build succeeds, update plan.md: change `- [ ] Build verification` to `- [x] Build verification`
3. If the build fails:
a. Read the error messages carefully
b. Read the relevant source files
c. Fix the errors in accordance with the plan and AGENTS.md conventions
d. Rebuild and repeat until the build passes
e. Update plan.md status when done
Rules:
- Only fix build errors, do not refactor or improve code
- Follow AGENTS.md conventions
- If build fails with file-locked errors (C1041, LNK1104), STOP and report - do not retry
When finished, report the build result.
```
## Phase 6: Code Review Loop
After build verification passes, run up to 3 review-fix iterations to improve code quality. Set iteration counter `R = 1`.
### Review Loop
```
LOOP:
1. Run review agent (Step 6a) with iteration R
2. Read review<R>.md verdict:
- "APPROVED" → go to FINISH
- Has improvement suggestions → run fix agent (Step 6b)
3. After fix agent completes and build passes:
R = R + 1
If R > 3 → go to FINISH (stop iterating, accept current state)
Otherwise → go to step 1
FINISH:
- Update plan.md: change `- [ ] Code review` to `- [x] Code review`
- Proceed to Completion
```
### Step 6a: Code Review Agent
```text
You are a code review agent for Telegram Desktop (C++ / Qt).
Read these files:
- .ai/<PROJECT>/<LETTER>/context.md - Codebase context
- .ai/<PROJECT>/<LETTER>/plan.md - Implementation plan
- REVIEW.md - Style and formatting rules to enforce
<if R > 1, also read:>
- .ai/<PROJECT>/<LETTER>/review<R-1>.md - Previous review (to see what was already addressed)
Then run `git diff` to see all uncommitted changes made by the implementation. Implementation agents do not commit, so `git diff` shows exactly the current feature's changes.
Then read the modified source files in full to understand changes in context.
Perform a thorough code review.
REVIEW CRITERIA (in order of importance):
1. **Correctness and safety**: Obvious logic errors, missing null checks at API boundaries, potential crashes, use-after-free, dangling references, race conditions. This is the highest priority — bugs and safety issues must be caught first. Do NOT nitpick internal code that relies on framework guarantees.
2. **Dead code**: Any code added or left behind that is never called or used, within the scope of the changes. Unused variables, unreachable branches, leftover scaffolding.
3. **Redundant changes**: Changes in the diff that have no functional effect — moving declarations or code blocks to a different location without reason, reformatting untouched code, reordering includes or fields with no purpose. Every line in the diff should serve the feature. If a file appears in `git diff` but contains only no-op rearrangements, flag it for revert.
4. **Code duplication**: Unnecessary repetition of logic that should be shared. Look for near-identical blocks that differ only in minor details and could be unified.
5. **Wrong placement**: Code added to a module where it doesn't logically belong. If another existing module is a clearly better fit for the new code, flag it. Consider the existing module boundaries and responsibilities visible in context.md.
6. **Function decomposition**: For longer functions (roughly 50+ lines), consider whether a logical sub-task could be cleanly extracted into a separate function. This is NOT a hard rule — a 100-line function that flows naturally and isn't easily divisible is perfectly fine. But sometimes even a 20-line function contains a clear isolated subtask that reads better as two 10-line functions. The key is to think about it each time: does extracting improve readability and reduce cognitive load, or does it just scatter logic across call sites for no real benefit? Only suggest extraction when there's a genuinely self-contained piece of logic with a clear name and purpose.
7. **Module structure**: Only in exceptional cases — if a large amount of newly added code (hundreds of lines) is logically distinct from the rest of its host module, suggest extracting it into a new module. But do NOT suggest new modules lightly: every module adds significant build overhead due to PCH and heavy template usage. Only suggest this when the new code is both large enough AND logically separated enough to justify it. At the same time, don't let modules grow into multi-thousand-line monoliths either.
8. **Style compliance**: Verify adherence to REVIEW.md rules (empty line before closing brace, operators at start of continuation lines, minimize type checks with direct cast instead of is+as, no if-with-initializer when simpler alternatives exist) and AGENTS.md conventions (no unnecessary comments, `auto` usage, no hardcoded sizes — must use .style definitions), etc.
IMPORTANT GUIDELINES:
- Review ONLY the changes made, not pre-existing code in the repository.
- Be pragmatic. Don't suggest changes for the sake of it. Each suggestion should have a clear, concrete benefit.
- Don't suggest adding comments, docstrings, or type annotations — the codebase style avoids these.
- Don't suggest error handling for impossible scenarios or over-engineering.
Write your review to: .ai/<PROJECT>/<LETTER>/review<R>.md
The review document should contain:
## Code Review - Iteration <R>
## Summary
<1-2 sentence overall assessment>
## Verdict: <APPROVED or NEEDS_CHANGES>
<If APPROVED, stop here. Everything looks good.>
<If NEEDS_CHANGES, continue with:>
## Changes Required
### <Issue 1 title>
- **Category**: <dead code | duplication | wrong placement | function decomposition | module structure | style | correctness>
- **File(s)**: <file paths>
- **Problem**: <clear description of what's wrong>
- **Fix**: <specific description of what to change>
### <Issue 2 title>
...
Keep the list focused. Only include issues that genuinely improve the code. If you find yourself listing more than ~5-6 issues, prioritize the most impactful ones.
When finished, report your verdict clearly as: APPROVED or NEEDS_CHANGES.
```
### Step 6b: Review Fix Agent
```text
You are a review fix agent. You implement improvements identified during code review.
Read these files:
- .ai/<PROJECT>/<LETTER>/context.md - Codebase context
- .ai/<PROJECT>/<LETTER>/plan.md - Original implementation plan
- .ai/<PROJECT>/<LETTER>/review<R>.md - Code review with required changes
Then read the source files mentioned in the review.
YOUR TASK: Implement ALL changes listed in review<R>.md.
For each issue in the review:
1. Read the relevant source file(s).
2. Make the specified change.
3. Verify the change makes sense in context.
After all changes are made:
1. Build (from repository root): cmake --build ./out --config Debug --target Telegram
2. If the build fails, fix build errors and rebuild until it passes.
3. If build fails with file-locked errors (C1041, LNK1104), STOP and report - do not retry.
Rules:
- Implement exactly the changes from the review, nothing more.
- Follow AGENTS.md coding conventions.
- Do NOT modify .ai/ files.
When finished, report what changes were made.
```
## Completion
When all phases including build verification and code review are done:
1. Read the final `plan.md` and report the summary to the user.
2. Show which files were modified/created.
3. Note any issues encountered during implementation.
4. Summarize code review iterations: how many rounds, what was found and fixed, or if it was approved on first pass.
5. Calculate and display the total elapsed time since `$START_TIME` (format as `Xh Ym Zs`, omitting zero components — e.g. `12m 34s` or `1h 5m 12s`).
6. Remind the user of the project name so they can request follow-up tasks within the same project.
## Error Handling
- If any phase fails or gets stuck, report the issue to the user and ask how to proceed.
- If context.md or plan.md is not written properly by a phase, re-run that phase with more specific instructions.
- If build errors persist after the build phase's attempts, report the remaining errors to the user.
- If a review fix phase introduces new build errors that it cannot resolve, report to the user.
## Reasoning Effort
Phases 2 (Plan), 3 (Assessment), and 6a (Review) require elevated reasoning. Pass `-c model_reasoning_effort="xhigh"` on those `codex exec` invocations. All other phases use the default reasoning effort.
## Example Runner Commands
```powershell
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-1-context.jsonl
codex exec --json -C <REPO_ROOT> -c model_reasoning_effort="xhigh" "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-2-plan.jsonl
codex exec --json -C <REPO_ROOT> -c model_reasoning_effort="xhigh" "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-3-assess.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-4-impl-N.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-5-build.jsonl
codex exec --json -C <REPO_ROOT> -c model_reasoning_effort="xhigh" "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-6a-review-R.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-6b-fix-R.jsonl
```
@@ -1,6 +1,6 @@
---
name: task-think
description: Orchestrate a multi-phase implementation workflow for this repository with artifact files under .ai/<project-name>/<letter>/ and optional fresh codex exec child runs per phase. Use when the user wants one prompt to drive context gathering, planning, implementation, verification, and review iterations while keeping the main session context clean.
description: Orchestrate a multi-phase implementation workflow for this repository with artifact files under .ai/<project-name>/<letter>/ and fresh codex exec child runs per phase. Use when the user wants one prompt to drive context gathering, planning, plan assessment, implementation, build verification, and review iterations while keeping the main session context clean.
---
# Task Pipeline
@@ -12,7 +12,7 @@ Run a full implementation workflow with repository artifacts and clear phase bou
Collect:
- task description
- optional project name (if missing, derive a short kebab-case name)
- optional constraints (files, architecture, deadlines, risk tolerance)
- optional constraints (files, architecture, risk tolerance)
- optional screenshot paths
If screenshots are attached in UI but not present as files, write a brief textual summary in `.ai/<project-name>/about.md` so child runs can consume the requirements.
@@ -28,11 +28,13 @@ Project structure:
a/ # First task
context.md # Gathered codebase context for this task
plan.md # Implementation plan
review.md # Code review document
review1.md # Code review documents (up to 3 iterations)
review2.md
review3.md
b/ # Follow-up task
context.md
plan.md
review.md
review1.md
c/ # Another follow-up task
...
```
@@ -46,41 +48,55 @@ Create and maintain:
- `.ai/<project-name>/about.md`
- `.ai/<project-name>/<letter>/context.md`
- `.ai/<project-name>/<letter>/plan.md`
- `.ai/<project-name>/<letter>/implementation.md`
- `.ai/<project-name>/<letter>/review.md`
- `.ai/<project-name>/<letter>/review<R>.md` (up to 3 review iterations)
- `.ai/<project-name>/<letter>/logs/phase-*.jsonl` (when running child `codex exec`)
## Execution Mode
## Phases
Run `codex exec --json` child sessions for each phase.
The workflow runs these phases sequentially via `codex exec --json` child sessions:
## Fresh-Run Mode Procedure
1. Detect follow-up vs new project (check if first token of task description matches an existing project name with `about.md`).
2. For new projects: pick unique short name, create `.ai/<project-name>/` and `.ai/<project-name>/a/`.
3. For follow-up tasks: find latest task letter, create `.ai/<project-name>/<next-letter>/`.
4. Run child phase sessions sequentially, waiting for each to finish.
5. After each phase, validate artifact file exists and has substantive content.
6. Summarize status in the parent session after each phase.
7. Stop immediately on blocking errors and report exact blocker.
1. **Phase 0: Setup** — Record start time, detect follow-up vs new project, create directories.
2. **Phase 1: Context Gathering** — Read codebase, write `about.md` and `context.md`. (Phase 1F for follow-ups.)
3. **Phase 2: Planning** — Read context, write detailed `plan.md` with numbered steps grouped into phases.
4. **Phase 3: Plan Assessment** — Review and refine the plan for correctness, completeness, code quality, and phase sizing.
5. **Phase 4: Implementation** — One child session per plan phase. Each implements only its assigned phase and updates `plan.md` status.
6. **Phase 5: Build Verification** — Build the project, fix any build errors. Skip if no source code was modified.
7. **Phase 6: Code Review Loop** — Up to 3 review-fix iterations:
- 6a: Review agent writes `review<R>.md` with verdict (APPROVED or NEEDS_CHANGES).
- 6b: Fix agent implements review changes and rebuilds.
- Loop until APPROVED or R > 3.
Use the phase prompt templates in `PROMPTS.md`.
## Execution Mode
Run `codex exec --json` child sessions for each phase. Wait for each to finish before starting the next. After each phase, validate that the expected artifact file exists and has substantive content.
Phases that require elevated reasoning (Planning, Plan Assessment, Code Review) must use `-c model_reasoning_effort="xhigh"`. See example commands in `PROMPTS.md`.
## Verification Rules
- If build or test commands fail due to file locks or access-denied outputs, stop and ask the user to close locking processes before retrying.
- If build or test commands fail due to file locks or access-denied outputs (C1041, LNK1104), stop and ask the user to close locking processes before retrying.
- Never claim completion without:
- implemented code changes present
- build/test attempt results recorded
- build attempt results recorded
- review pass documented with any follow-up fixes
## Completion Criteria
Mark complete only when:
- plan phases are done
- verification results are recorded
- review issues are addressed or explicitly deferred with rationale
- Remind the user of the project name so they can request follow-up tasks within the same project.
- All plan phases are done
- Build verification is recorded
- Review issues are addressed or explicitly deferred with rationale
- Display total elapsed time since start (format: `Xh Ym Zs`, omitting zero components)
- Remind the user of the project name so they can request follow-up tasks within the same project
## Error Handling
- If any phase fails or gets stuck, report the issue to the user and ask how to proceed.
- If context.md or plan.md is not written properly by a phase, re-run that phase with more specific instructions.
- If build errors persist after the build phase's attempts, report the remaining errors to the user.
- If a review fix phase introduces new build errors that it cannot resolve, report to the user.
## User Invocation
+11 -11
View File
@@ -82,10 +82,10 @@ You are a context-gathering agent for a large C++ codebase (Telegram Desktop).
TASK: <paste the user's task description here>
YOUR JOB: Read CLAUDE.md, inspect the codebase, find ALL files and code relevant to this task, and write two documents.
YOUR JOB: Read AGENTS.md, inspect the codebase, find ALL files and code relevant to this task, and write two documents.
Steps:
1. Read CLAUDE.md for project conventions and build instructions.
1. Read AGENTS.md for project conventions and build instructions.
2. Search the codebase for files, classes, functions, and patterns related to the task.
3. Read all potentially relevant files. Be thorough - read more rather than less.
4. For each relevant file, note:
@@ -142,7 +142,7 @@ NEW TASK: <paste the follow-up task description here>
YOUR JOB: Read the existing project state, gather any additional context needed, and produce fresh documents for the new task.
Steps:
1. Read CLAUDE.md for project conventions and build instructions.
1. Read AGENTS.md for project conventions and build instructions.
2. Read .ai/<project-name>/about.md — this is the project-level blueprint describing everything done so far.
3. Read .ai/<project-name>/<previous-letter>/context.md — this is the previous task's gathered context.
4. Understand what has already been implemented by reading the actual source files referenced in about.md and the previous context.
@@ -268,7 +268,7 @@ Use /ultrathink to assess the plan:
1. **Correctness**: Are the file paths and line references accurate? Does the plan reference real functions and types?
2. **Completeness**: Are there missing steps? Edge cases not handled?
3. **Code quality**: Will the plan minimize code duplication? Does it follow existing codebase patterns from CLAUDE.md?
3. **Code quality**: Will the plan minimize code duplication? Does it follow existing codebase patterns from AGENTS.md?
4. **Design**: Could the approach be improved? Are there better patterns already used in the codebase?
5. **Phase sizing**: Each phase should be implementable by a single agent in one session. If a phase has more than ~8-10 substantive code changes, split it further.
@@ -305,7 +305,7 @@ YOUR TASK: Implement ONLY Phase <N> from the plan:
Rules:
- Follow the plan precisely
- Follow CLAUDE.md coding conventions (no comments except complex algorithms, use auto, empty line before closing brace, etc.)
- Follow AGENTS.md coding conventions (no comments except complex algorithms, use auto, empty line before closing brace, etc.)
- Do NOT modify .ai/ files except to update the Status section in plan.md
- When done, update plan.md Status section: change `- [ ] Phase <N>: ...` to `- [x] Phase <N>: ...`
- Do NOT work on other phases
@@ -334,18 +334,18 @@ Read these files:
The implementation is complete. Your job is to build the project and fix any build errors.
Steps:
1. Run: cmake --build "c:\Telegram\tdesktop\out" --config Debug --target Telegram
1. Run (from repository root): cmake --build ./out --config Debug --target Telegram
2. If the build succeeds, update plan.md: change `- [ ] Build verification` to `- [x] Build verification`
3. If the build fails:
a. Read the error messages carefully
b. Read the relevant source files
c. Fix the errors in accordance with the plan and CLAUDE.md conventions
c. Fix the errors in accordance with the plan and AGENTS.md conventions
d. Rebuild and repeat until the build passes
e. Update plan.md status when done
Rules:
- Only fix build errors, do not refactor or improve code
- Follow CLAUDE.md conventions
- Follow AGENTS.md conventions
- If build fails with file-locked errors (C1041, LNK1104), STOP and report - do not retry
When finished, report the build result.
@@ -411,7 +411,7 @@ REVIEW CRITERIA (in order of importance):
7. **Module structure**: Only in exceptional cases — if a large amount of newly added code (hundreds of lines) is logically distinct from the rest of its host module, suggest extracting it into a new module. But do NOT suggest new modules lightly: every module adds significant build overhead due to PCH and heavy template usage. Only suggest this when the new code is both large enough AND logically separated enough to justify it. At the same time, don't let modules grow into multi-thousand-line monoliths either.
8. **Style compliance**: Verify adherence to REVIEW.md rules (empty line before closing brace, operators at start of continuation lines, minimize type checks with direct cast instead of is+as, no if-with-initializer when simpler alternatives exist) and CLAUDE.md conventions (no unnecessary comments, `auto` usage, no hardcoded sizes — must use .style definitions), etc.
8. **Style compliance**: Verify adherence to REVIEW.md rules (empty line before closing brace, operators at start of continuation lines, minimize type checks with direct cast instead of is+as, no if-with-initializer when simpler alternatives exist) and AGENTS.md conventions (no unnecessary comments, `auto` usage, no hardcoded sizes — must use .style definitions), etc.
IMPORTANT GUIDELINES:
- Review ONLY the changes made, not pre-existing code in the repository.
@@ -474,13 +474,13 @@ For each issue in the review:
3. Verify the change makes sense in context.
After all changes are made:
1. Build: cmake --build "c:\Telegram\tdesktop\out" --config Debug --target Telegram
1. Build (from repository root): cmake --build ./out --config Debug --target Telegram
2. If the build fails, fix build errors and rebuild until it passes.
3. If build fails with file-locked errors (C1041, LNK1104), STOP and report - do not retry.
Rules:
- Implement exactly the changes from the review, nothing more.
- Follow CLAUDE.md coding conventions.
- Follow AGENTS.md coding conventions.
- Do NOT modify .ai/ files.
When finished, report what changes were made.
-227
View File
@@ -1,227 +0,0 @@
# Phase Prompts
Use these templates for `codex exec --json` child runs. Replace `<TASK>`, `<PROJECT>`, `<LETTER>`, and `<REPO_ROOT>`.
## Phase 0: Setup
Before running any phase prompts, the orchestrator must determine whether this is a new project or a follow-up task.
**Follow-up detection:**
1. Extract the first word/token from the task description. Call it `FIRST_TOKEN`.
2. Check if `.ai/<FIRST_TOKEN>/about.md` exists.
3. If it exists: this is a **follow-up task**. The project name is `FIRST_TOKEN`. The task description is everything after `FIRST_TOKEN`.
4. If it does not exist: this is a **new project**. The full input is the task description.
**For new projects:**
- List existing `.ai/` folders to pick a unique short name (1-2 lowercase words, hyphen-separated).
- Create `.ai/<PROJECT>/` and `.ai/<PROJECT>/a/` and `logs/`.
- Set `<LETTER>` = `a`.
**For follow-up tasks:**
- Scan `.ai/<PROJECT>/` for existing task folders (`a/`, `b/`, ...). Find the latest one (highest letter).
- The new task letter = next letter in sequence.
- Create `.ai/<PROJECT>/<LETTER>/` and `logs/`.
Then proceed to Phase 1. Follow-up tasks do NOT skip context gathering — they go through a modified version of it.
## Phase 1: Context (New Project, letter = `a`)
```text
You are the context phase for task "<TASK>" in repository <REPO_ROOT>.
Read CLAUDE.md for the basic coding rules and guidelines.
Read AGENTS.md and all relevant source files. Write TWO documents:
### File 1: .ai/<PROJECT>/about.md
NOTE: This file is NOT used by any agent in the current task. It exists solely as a starting point for a FUTURE follow-up task's context gatherer. No planning, implementation, or review agent will ever read it.
Write it as if the project is already fully implemented and working. It should contain:
- **Project**: What this project does (feature description, goals, scope)
- **Architecture**: High-level architectural decisions, which modules are involved, how they interact
- **Key Design Decisions**: Important choices made about the approach
- **Relevant Codebase Areas**: Which parts of the codebase this project touches, key types and APIs involved
Do NOT include temporal state like "Current State", "Pending Changes", "Not yet implemented", "TODO", or any other framing that distinguishes between "done" and "not done". Describe the project as a complete, coherent whole.
### File 2: .ai/<PROJECT>/a/context.md
This is the PRIMARY document — all downstream agents (planning, implementation, review) will read ONLY this file. It must be completely self-contained. Include:
1. Task description restated clearly
2. Relevant files with line ranges and why they matter
3. Existing patterns to follow (with code snippets)
4. Data structures, types, classes
5. API methods (from api.tl if applicable)
6. UI styles (from .style files if applicable)
7. Localization (from lang.strings if applicable)
8. Build info and verification hooks
9. Reference implementations of similar features
10. Risks and unknowns
Be extremely thorough. Another agent with NO prior context will read this file and must be able to understand everything needed to implement the task.
Do not implement code in this phase.
```
## Phase 1F: Context (Follow-up Task, letter = `b`, `c`, ...)
```text
You are the context phase for a follow-up task on an existing project in repository <REPO_ROOT>.
NEW TASK: <TASK>
Read CLAUDE.md for the basic coding rules and guidelines.
Steps:
1. Read AGENTS.md for project conventions.
2. Read .ai/<PROJECT>/about.md — the project-level blueprint describing everything done so far.
3. Read .ai/<PROJECT>/<PREV_LETTER>/context.md — the previous task's gathered context.
4. Understand what has already been implemented by reading the actual source files referenced in about.md and the previous context.
5. Search the codebase for any ADDITIONAL files, classes, functions, and patterns relevant to the new task but not already covered.
6. Read all newly relevant files thoroughly.
Write TWO files:
### File 1: .ai/<PROJECT>/about.md (REWRITE)
NOTE: This file is NOT used by any agent in the current task. It exists solely as a starting point for a FUTURE follow-up task's context gatherer. You are rewriting it now so that the next follow-up has an accurate project overview to start from.
REWRITE this file (not append). The new about.md must be a single coherent document that describes the project as if everything — including this new task's changes — is already fully implemented and working.
It should incorporate:
- Everything from the old about.md that is still accurate and relevant
- The new task's functionality described as part of the project (not as "changes to make")
- Any changed design decisions or architectural updates from the new task requirements
It should NOT contain:
- Any temporal state: "Current State", "Pending Changes", "TODO", "Not yet implemented"
- History of how requirements changed between tasks
- References to "the old approach" vs "the new approach"
- Task-by-task changelog or timeline
- Any distinction between "what was done before" and "what this task adds"
- Information that contradicts the new task requirements
### File 2: .ai/<PROJECT>/<LETTER>/context.md
This is the PRIMARY document — all downstream agents (planning, implementation, review) will read ONLY this file. It must be completely self-contained. about.md will NOT be available to them.
It should contain:
- **Task Description**: The new task restated clearly, with enough project background that an implementation agent can understand it without reading any other .ai/ files
- **Relevant Files**: Every file path with line ranges relevant to THIS task (including files modified by previous tasks and any newly relevant files)
- **Key Code Patterns**: How similar things are done in the codebase
- **Data Structures**: Relevant types, structs, classes
- **API Methods**: Any TL schema methods involved
- **UI Styles**: Any relevant style definitions
- **Localization**: Any relevant string keys
- **Build Info**: Build command and any special notes
- **Reference Implementations**: Similar features that can serve as templates
Be extremely thorough. Another agent with NO prior context will read ONLY this file and must be able to understand everything needed to implement the new task. Do NOT assume the reader has seen about.md or any previous task files.
Do not implement code in this phase.
```
## Phase 2: Plan
```text
You are the planning phase for task "<TASK>" in repository <REPO_ROOT>.
Read CLAUDE.md for the basic coding rules and guidelines.
Read:
- .ai/<PROJECT>/<LETTER>/context.md
Then read the specific source files referenced in context.md to understand the code deeply.
Create:
- .ai/<PROJECT>/<LETTER>/plan.md
Plan requirements:
1. Concrete file-level edits
2. Ordered phases (each phase implementable by a single agent, roughly <=8 steps)
3. Verification commands
4. Rollback/risk notes
5. Status section with checklist of phases, build verification, and code review
```
## Phase 3: Implement
```text
You are the implementation phase for task "<TASK>" in repository <REPO_ROOT>.
Read CLAUDE.md for the basic coding rules and guidelines.
Read:
- .ai/<PROJECT>/<LETTER>/context.md
- .ai/<PROJECT>/<LETTER>/plan.md
Implement the plan in code. Then write:
- .ai/<PROJECT>/<LETTER>/implementation.md
Include:
1. Files changed
2. What was implemented
3. Any deviations from plan and why
4. Update plan.md Status section to mark completed phases
```
## Phase 4: Verify
```text
You are the verification phase for task "<TASK>" in repository <REPO_ROOT>.
Read CLAUDE.md for the basic coding rules and guidelines.
Read:
- .ai/<PROJECT>/<LETTER>/plan.md
- .ai/<PROJECT>/<LETTER>/implementation.md
Run the relevant build/test commands from AGENTS.md and plan.md.
Append results to:
- .ai/<PROJECT>/<LETTER>/implementation.md
If blocked by locked files or access errors, stop and report exact blocker.
```
## Phase 5: Review
```text
You are the review phase for task "<TASK>" in repository <REPO_ROOT>.
Read AGENTS.md for the basic coding rules and guidelines.
Read REVIEW.md for the style and formatting rules you must enforce.
Read:
- .ai/<PROJECT>/<LETTER>/context.md
- .ai/<PROJECT>/<LETTER>/plan.md
- .ai/<PROJECT>/<LETTER>/implementation.md
Run `git diff` to see all uncommitted changes made by the implementation. Implementation phases do not commit, so `git diff` shows exactly the current feature's changes. Then read the modified source files in full.
Perform a code review using these criteria (in order of importance):
1. Correctness and safety: logic errors, null-check gaps at API boundaries, crashes, use-after-free, dangling references, race conditions.
2. Dead code: code added or left behind that is never called or used. Unused variables, unreachable branches, leftover scaffolding.
3. Redundant changes: changes in the diff with no functional effect — moving declarations or code blocks without reason, reformatting untouched code, reordering includes or fields with no purpose. Every line in the diff should serve the feature. If a file appears in `git diff` but contains only no-op rearrangements, flag it for revert.
4. Code duplication: unnecessary repetition of logic that should be shared.
5. Wrong placement: code added to a module where it doesn't logically belong.
6. Function decomposition: for longer functions (~50+ lines), consider whether a sub-task could be cleanly extracted. Only suggest when there is a genuinely self-contained piece of logic.
7. Module structure: only flag if a large amount of new code (hundreds of lines) is logically distinct from its host module.
8. Style compliance: verify adherence to REVIEW.md rules and AGENTS.md conventions.
Write:
- .ai/<PROJECT>/<LETTER>/review.md
If issues are found, implement fixes and update implementation.md/review.md with final status.
```
## Example Runner Commands
```powershell
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-1-context.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-2-plan.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-3-implement.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-4-verify.jsonl
codex exec --json -C <REPO_ROOT> "<PHASE_PROMPT>" | Tee-Object .ai/<PROJECT>/<LETTER>/logs/phase-5-review.jsonl
```
-105
View File
@@ -1,105 +0,0 @@
---
description: For tasks requiring sending Telegram server API requests or working with generated API types.
globs:
alwaysApply: false
---
# Telegram Desktop API Usage
## API Schema
The API definitions are described using [TL Language]\(https:/core.telegram.org/mtproto/TL) in two main schema files:
1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`**
* Defines the core MTProto protocol types and methods used for basic communication, encryption, authorization, service messages, etc.
* Some fundamental types and methods from this schema (like basic types, RPC calls, containers) are often implemented directly in the C++ MTProto core (`SourceFiles/mtproto/`) and may be skipped during the C++ code generation phase.
* Other parts of `mtproto.tl` might still be processed by the code generator.
2. **`Telegram/SourceFiles/mtproto/scheme/api.tl`**
* Defines the higher-level Telegram API layer, including all the methods and types related to chat functionality, user profiles, messages, channels, stickers, etc.
* This is the primary schema used when making functional API requests within the application.
Both files use the same TL syntax to describe API methods (functions) and types (constructors).
## Code Generation
A custom code generation tool processes `api.tl` (and parts of `mtproto.tl`) to create corresponding C++ classes and types. These generated headers are typically included via the Precompiled Header (PCH) for the main `Telegram` project.
Generated types often follow the pattern `MTP[Type]` (e.g., `MTPUser`, `MTPMessage`) and methods correspond to functions within the `MTP` namespace or related classes (e.g., `MTPmessages_SendMessage`).
## Making API Requests
API requests are made using a standard pattern involving the `api()` object (providing access to the `MTP::Instance`), the generated `MTP...` request object, callback handlers for success (`.done()`) and failure (`.fail()`), and the `.send()` method.
Here's the general structure:
```cpp
// Include necessary headers if not already in PCH
// Obtain the API instance (usually via api() or MTP::Instance::Get())
api().request(MTPnamespace_MethodName(
// Constructor arguments based on the api.tl definition for the method
MTP_flags(flags_value), // Use MTP_flags if the method has flags
MTP_inputPeer(peer), // Use MTP_... types for parameters
MTP_string(messageText),
MTP_long(randomId),
// ... other arguments matching the TL definition
MTP_vector<MTPMessageEntity>() // Example for a vector argument
)).done([=]\(const MTPResponseType &result) {
// Handle the successful response (result).
// 'result' will be of the C++ type corresponding to the TL type
// specified after the '=' in the api.tl method definition.
// How to access data depends on whether the TL type has one or multiple constructors:
// 1. Multiple Constructors (e.g., User = User | UserEmpty):
// Use .match() with lambdas for each constructor:
result.match([&]\(const MTPDuser &data) {
/* use data.vfirst_name().v, etc. */
}, [&]\(const MTPDuserEmpty &data) {
/* handle empty user */
});
// Alternatively, check the type explicitly and use the constructor getter:
if (result.type() == mtpc_user) {
const auto &data = result.c_user(); // Asserts if type is not mtpc_user!
// use data.vfirst_name().v
} else if (result.type() == mtpc_userEmpty) {
const auto &data = result.c_userEmpty();
// handle empty user
}
// 2. Single Constructor (e.g., Messages = messages { msgs: vector<Message> }):
// Use .match() with a single lambda:
result.match([&]\(const MTPDmessages &data) { /* use data.vmessages().v */ });
// Or check the type explicitly and use the constructor getter:
if (result.type() == mtpc_messages) {
const auto &data = result.c_messages(); // Asserts if type is not mtpc_messages!
// use data.vmessages().v
}
// Or use the shortcut .data() for single-constructor types:
const auto &data = result.data(); // Only works for single-constructor types!
// use data.vmessages().v
}).fail([=]\(const MTP::Error &error) {
// Handle the API error (error).
// 'error' is an MTP::Error object containing the error code (error.type())
// and description (error.description()). Check for specific error strings.
if (error.type() == u"FLOOD_WAIT_X"_q) {
// Handle flood wait
} else {
Ui::show(Box<InformBox>(Lang::Hard::ServerError())); // Example generic error handling
}
}).handleFloodErrors().send(); // handleFloodErrors() is common, then send()
```
**Key Points:**
* Always refer to `Telegram/SourceFiles/mtproto/scheme/api.tl` for the correct method names, parameters (names and types), and response types.
* Use the generated `MTP...` types/classes for request parameters (e.g., `MTP_int`, `MTP_string`, `MTP_bool`, `MTP_vector`, `MTPInputUser`, etc.) and response handling.
* The `.done()` lambda receives the specific C++ `MTP...` type corresponding to the TL return type.
* For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&]\(const MTPDuser &d){ ... }, [&]\(const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch).
* For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&]\(const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly.
* The `.fail()` lambda receives an `MTP::Error` object. Check `error.type()` against known error strings (often defined as constants or using `u"..."_q` literals).
* Directly construct the `MTPnamespace_MethodName(...)` object inside `request()`.
* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling.
-164
View File
@@ -1,164 +0,0 @@
---
description: For tasks requiring changing or adding user facing phrases and text parts.
globs:
alwaysApply: false
---
# Telegram Desktop Localization
## Coding Style Note
**Use `auto`:** In the actual codebase, variable types are almost always deduced using `auto` (or `const auto`, `const auto &`) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer `auto` in practice.
```cpp
// Prefer this:
auto currentTitle = tr::lng_settings_title(tr::now);
auto nameProducer = GetNameProducer(); // Returns rpl::producer<...>
// Instead of this:
QString currentTitle = tr::lng_settings_title(tr::now);
rpl::producer<QString> nameProducer = GetNameProducer();
```
## String Resource File
Base user-facing English strings are defined in the `lang.strings` file:
`Telegram/Resources/langs/lang.strings`
This file uses a key-value format with named placeholders:
```
"lng_settings_title" = "Settings";
"lng_confirm_delete_item" = "Are you sure you want to delete {item_name}?";
"lng_files_selected" = "{count} files selected"; // Simple count example (see Pluralization)
```
Placeholders are enclosed in curly braces, e.g., `{name}`, `{user}`. A special placeholder `{count}` is used for pluralization rules.
### Pluralization
For keys that depend on a number (using the `{count}` placeholder), English typically requires two forms: singular and plural. These are defined in `lang.strings` using `#one` and `#other` suffixes:
```
"lng_files_selected#one" = "{count} file selected";
"lng_files_selected#other" = "{count} files selected";
```
While only `#one` and `#other` are defined in the base `lang.strings`, the code generation process creates C++ accessors for all six CLDR plural categories (`#zero`, `#one`, `#two`, `#few`, `#many`, `#other`) to support languages with more complex pluralization rules.
## Translation Process
While `lang.strings` provides the base English text and the keys, the actual translations are managed via Telegram's translations platform (translations.telegram.org) and loaded dynamically at runtime from the API. The keys from `lang.strings` (including the `#one`/`#other` variants) are used on the platform.
## Code Generation
A code generation tool processes `lang.strings` to create C++ structures and accessors within the `tr` namespace. These allow type-safe access to strings and handling of placeholders and pluralization. Generated keys typically follow the pattern `tr::lng_key_name`.
## String Usage in Code
Strings are accessed in C++ code using the generated objects within the `tr::` namespace. There are two main ways to use them: reactively (returning an `rpl::producer`) or immediately (returning the current value).
### 1. Reactive Usage (rpl::producer)
Calling a generated string function directly returns a reactive producer, typically `rpl::producer<QString>`. This producer automatically updates its value whenever the application language changes.
```cpp
// Key: "settings_title" = "Settings";
auto titleProducer = tr::lng_settings_title(); // Type: rpl::producer<QString>
// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?";
auto itemNameProducer = /* ... */; // Type: rpl::producer<QString>
auto confirmationProducer = tr::lng_confirm_delete_item( // Type: rpl::producer<QString>
tr::now, // NOTE: tr::now is NOT passed here for reactive result
lt_item_name,
std::move(itemNameProducer)); // Placeholder producers should be moved
```
### 2. Immediate Usage (Current Value)
Passing `tr::now` as the first argument retrieves the string's current value in the active language (typically as a `QString`).
```cpp
// Key: "settings_title" = "Settings";
auto currentTitle = tr::lng_settings_title(tr::now); // Type: QString
// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?";
const auto currentItemName = QString("My Document"); // Type: QString
auto currentConfirmation = tr::lng_confirm_delete_item( // Type: QString
tr::now, // Pass tr::now for immediate value
lt_item_name, currentItemName); // Placeholder value is a direct QString (or convertible)
```
### 3. Placeholders (`{tag}`)
Placeholders like `{item_name}` are replaced by providing arguments after `tr::now` (for immediate) or as the initial arguments (for reactive). A corresponding `lt_tag_name` constant is passed before the value.
* **Immediate:** Pass the direct value (e.g., `QString`, `int`).
* **Reactive:** Pass an `rpl::producer` of the corresponding type (e.g., `rpl::producer<QString>`). Remember to `std::move` the producer or use `rpl::duplicate` if you need to reuse the original producer afterwards.
### 4. Pluralization (`{count}`)
Keys using `{count}` require a numeric value for the `lt_count` placeholder. The correct plural form (`#zero`, `#one`, ..., `#other`) is automatically selected based on this value and the current language rules.
* **Immediate (`tr::now`):** Pass a `float64` or `int` (which is auto-converted to `float64`).
```cpp
int count = 1;
auto filesText = tr::lng_files_selected(tr::now, lt_count, count); // Type: QString
count = 5;
filesText = tr::lng_files_selected(tr::now, lt_count, count); // Uses "files_selected#other"
```
* **Reactive:** Pass an `rpl::producer<float64>`. Use the `tr::to_count()` helper to convert an `rpl::producer<int>` or wrap a single value.
```cpp
// From an existing int producer:
auto countProducer = /* ... */; // Type: rpl::producer<int>
auto filesTextProducer = tr::lng_files_selected( // Type: rpl::producer<QString>
lt_count,
countProducer | tr::to_count()); // Use tr::to_count() for conversion
// From a single int value wrapped reactively:
int currentCount = 5;
auto filesTextProducerSingle = tr::lng_files_selected( // Type: rpl::producer<QString>
lt_count,
rpl::single(currentCount) | tr::to_count());
// Alternative for single values (less common): rpl::single(currentCount * 1.)
```
### 5. Custom Projectors
An optional final argument can be a projector function (like `Ui::Text::Upper` or `Ui::Text::WithEntities`) to transform the output.
* If the projector returns `OutputType`, the string function returns `OutputType` (immediate) or `rpl::producer<OutputType>` (reactive).
* Placeholder values must match the projector's *input* requirements. For `Ui::Text::WithEntities`, placeholders expect `TextWithEntities` (immediate) or `rpl::producer<TextWithEntities>` (reactive).
```cpp
// Immediate with Ui::Text::WithEntities projector
// Key: "user_posted_photo" = "{user} posted a photo";
const auto userName = TextWithEntities{ /* ... */ }; // Type: TextWithEntities
auto message = tr::lng_user_posted_photo( // Type: TextWithEntities
tr::now,
lt_user,
userName, // Must be TextWithEntities
Ui::Text::WithEntities); // Projector
// Reactive with Ui::Text::WithEntities projector
auto userNameProducer = /* ... */; // Type: rpl::producer<TextWithEntities>
auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer<TextWithEntities>
lt_user,
std::move(userNameProducer), // Move placeholder producers
Ui::Text::WithEntities); // Projector
```
## Key Summary
* Keys are defined in `Resources/langs/lang.strings` using `{tag}` placeholders.
* Plural keys use `{count}` and have `#one`/`#other` variants in `lang.strings`.
* Access keys via `tr::lng_key_name(...)` in C++.
* Call with `tr::now` as the first argument for the immediate `QString` (or projected type).
* Call without `tr::now` for the reactive `rpl::producer<QString>` (or projected type).
* Provide placeholder values (`lt_tag_name, value`) matching the usage (direct value for immediate, `rpl::producer` for reactive). Producers should typically be moved via `std::move`.
* For `{count}`:
* Immediate: Pass `int` or `float64`.
* Reactive: Pass `rpl::producer<float64>`, typically by converting an `int` producer using `| tr::to_count()`.
* Optional projector function as the last argument modifies the output type and required placeholder types.
* Actual translations are loaded at runtime from the API.
-216
View File
@@ -1,216 +0,0 @@
---
description:
globs:
alwaysApply: true
---
# RPL (Reactive Programming Library) Guide
## Coding Style Note
**Use `auto`:** In the actual codebase, variable types are almost always deduced using `auto` (or `const auto`, `const auto &`) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer `auto` in practice.
```cpp
// Prefer this:
auto intProducer = rpl::single(123);
const auto &lifetime = existingLifetime;
// Instead of this:
rpl::producer<int> intProducer = rpl::single(123);
const rpl::lifetime &lifetime = existingLifetime;
// Sometimes needed if deduction is ambiguous or needs help:
auto user = std::make_shared<UserData>();
auto data = QByteArray::fromHex("...");
```
## Introduction
RPL is the reactive programming library used in this project, residing in the `rpl::` namespace. It allows handling asynchronous streams of data over time.
The core concept is the `rpl::producer`, which represents a stream of values that can be generated over a certain lifetime.
## Producers: `rpl::producer<Type, Error = no_error>`
The fundamental building block is `rpl::producer<Type, Error>`. It produces values of `Type` and can optionally signal an error of type `Error`. By default, `Error` is `rpl::no_error`, indicating that the producer does not explicitly handle error signaling through this mechanism.
```cpp
// A producer that emits integers.
auto intProducer = /* ... */; // Type: rpl::producer<int>
// A producer that emits strings and can potentially emit a CustomError.
auto stringProducerWithError = /* ... */; // Type: rpl::producer<QString, CustomError>
```
Producers are typically lazy; they don't start emitting values until someone subscribes to them.
## Lifetime Management: `rpl::lifetime`
Reactive pipelines have a limited duration, managed by `rpl::lifetime`. An `rpl::lifetime` object essentially holds a collection of cleanup callbacks. When the lifetime ends (either explicitly destroyed or goes out of scope), these callbacks are executed, tearing down the associated pipeline and freeing resources.
```cpp
rpl::lifetime myLifetime;
// ... later ...
// myLifetime is destroyed, cleanup happens.
// Or, pass a lifetime instance to manage a pipeline's duration.
rpl::lifetime &parentLifetime = /* ... get lifetime from context ... */;
```
## Starting a Pipeline: `rpl::start_...`
To consume values from a producer, you start a pipeline using one of the `rpl::start_...` methods. These methods subscribe to the producer and execute callbacks for the events they handle.
The most common method is `rpl::on_next`:
```cpp
auto counter = /* ... */; // Type: rpl::producer<int>
rpl::lifetime lifetime;
// Counter is consumed here, use std::move if it's an l-value.
std::move(
counter
) | rpl::on_next([=]\(int nextValue) {
// Process the next integer value emitted by the producer.
qDebug() << "Received: " << nextValue;
}, lifetime); // Pass the lifetime to manage the subscription.
// Note: `counter` is now in a moved-from state and likely invalid.
// If you need to start the same producer multiple times, duplicate it:
// rpl::duplicate(counter) | rpl::on_next(...);
// If you DON'T pass a lifetime to a start_... method:
auto counter2 = /* ... */; // Type: rpl::producer<int>
rpl::lifetime subscriptionLifetime = std::move(
counter2
) | rpl::on_next([=]\(int nextValue) { /* ... */ });
// The returned lifetime MUST be stored. If it's discarded immediately,
// the subscription stops instantly.
// `counter2` is also moved-from here.
```
Other variants allow handling errors (`_error`) and completion (`_done`):
```cpp
auto dataStream = /* ... */; // Type: rpl::producer<QString, Error>
rpl::lifetime lifetime;
// Assuming dataStream might be used again, we duplicate it for the first start.
// If it's the only use, std::move(dataStream) would be preferred.
rpl::duplicate(
dataStream
) | rpl::on_error([=]\(Error &&error) {
// Handle the error signaled by the producer.
qDebug() << "Error: " << error.text();
}, lifetime);
// Using dataStream again, perhaps duplicated again or moved if last use.
rpl::duplicate(
dataStream
) | rpl::on_done([=] {
// Execute when the producer signals it's finished emitting values.
qDebug() << "Stream finished.";
}, lifetime);
// Last use of dataStream, so we move it.
std::move(
dataStream
) | rpl::on_next_error_done(
[=]\(QString &&value) { /* handle next value */ },
[=]\(Error &&error) { /* handle error */ },
[=] { /* handle done */ },
lifetime);
```
## Transforming Producers
RPL provides functions to create new producers by transforming existing ones:
* `rpl::map`: Transforms each value emitted by a producer.
```cpp
auto ints = /* ... */; // Type: rpl::producer<int>
// The pipe operator often handles the move implicitly for chained transformations.
auto strings = std::move(
ints // Explicit move is safer
) | rpl::map([](int value) {
return QString::number(value * 2);
}); // Emits strings like "0", "2", "4", ...
```
* `rpl::filter`: Emits only the values from a producer that satisfy a condition.
```cpp
auto ints = /* ... */; // Type: rpl::producer<int>
auto evenInts = std::move(
ints // Explicit move is safer
) | rpl::filter([](int value) {
return (value % 2 == 0);
}); // Emits only even numbers.
```
## Combining Producers
You can combine multiple producers into one:
* `rpl::combine`: Combines the latest values from multiple producers whenever *any* of them emits a new value. Requires all producers to have emitted at least one value initially.
While it produces a `std::tuple`, subsequent operators like `map`, `filter`, and `on_next` can automatically unpack this tuple into separate lambda arguments.
```cpp
auto countProducer = rpl::single(1); // Type: rpl::producer<int>
auto textProducer = rpl::single(u"hello"_q); // Type: rpl::producer<QString>
rpl::lifetime lifetime;
// rpl::combine takes producers by const-ref internally and duplicates,
// so move/duplicate is usually not strictly needed here unless you
// want to signal intent or manage the lifetime of p1/p2 explicitly.
auto combined = rpl::combine(
countProducer, // or rpl::duplicate(countProducer)
textProducer // or rpl::duplicate(textProducer)
);
// Starting the combined producer consumes it.
// The lambda receives unpacked arguments, not the tuple itself.
std::move(
combined
) | rpl::on_next([=]\(int count, const QString &text) {
// No need for std::get<0>(latest), etc.
qDebug() << "Combined: Count=" << count << ", Text=" << text;
}, lifetime);
// This also works with map, filter, etc.
std::move(
combined
) | rpl::filter([=]\(int count, const QString &text) {
return count > 0 && !text.isEmpty();
}) | rpl::map([=]\(int count, const QString &text) {
return text.repeated(count);
}) | rpl::on_next([=]\(const QString &result) {
qDebug() << "Mapped & Filtered: " << result;
}, lifetime);
```
* `rpl::merge`: Merges the output of multiple producers of the *same type* into a single producer. It emits a value whenever *any* of the source producers emits a value.
```cpp
auto sourceA = /* ... */; // Type: rpl::producer<QString>
auto sourceB = /* ... */; // Type: rpl::producer<QString>
// rpl::merge also duplicates internally.
auto merged = rpl::merge(sourceA, sourceB);
// Starting the merged producer consumes it.
std::move(
merged
) | rpl::on_next([=]\(QString &&value) {
// Receives values from either sourceA or sourceB as they arrive.
qDebug() << "Merged value: " << value;
}, lifetime);
```
## Key Concepts Summary
* Use `rpl::producer<Type, Error>` to represent streams of values.
* Manage subscription duration using `rpl::lifetime`.
* Pass `rpl::lifetime` to `rpl::start_...` methods.
* If `rpl::lifetime` is not passed, **store the returned lifetime** to keep the subscription active.
* Use operators like `| rpl::map`, `| rpl::filter` to transform streams.
* Use `rpl::combine` or `rpl::merge` to combine streams.
* When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer.
* These functions typically duplicate their input producers internally.
* Starting a pipeline consumes the producer; use `
-154
View File
@@ -1,154 +0,0 @@
---
description: For tasks requiring working with user facing UI components.
globs:
alwaysApply: false
---
# Telegram Desktop UI Styling
## Style Definition Files
UI element styles (colors, fonts, paddings, margins, icons, etc.) are defined in `.style` files using a custom syntax. These files are located alongside the C++ source files they correspond to within specific UI component directories (e.g., `Telegram/SourceFiles/ui/chat/chat.style`).
Definitions from other `.style` files can be included using the `using` directive at the top of the file:
```style
using "ui/basic.style";
using "ui/widgets/widgets.style";
```
The central definition of named colors happens in `Telegram/SourceFiles/ui/colors.palette`. This file allows for theme generation and loading colors from various sources.
### Syntax Overview
1. **Built-in Types:** The syntax recognizes several base types inferred from the value assigned:
* `int`: Integer numbers (e.g., `lineHeight: 20;`)
* `bool`: Boolean values (e.g., `useShadow: true;`)
* `pixels`: Pixel values, ending with `px` (e.g., `borderWidth: 1px;`). Generated as `int` in C++.
* `color`: Named colors defined in `colors.palette` (e.g., `background: windowBg;`)
* `icon`: Defined inline using a specific syntax (see below). Generates `style::icon`.
* `margins`: Four pixel values for margins or padding. Requires `margins(top, right, bottom, left)` syntax (e.g., `margin: margins(10px, 5px, 10px, 5px);` or `padding: margins(8px, 8px, 8px, 8px);`). Generates `style::margins` (an alias for `QMargins`).
* `size`: Two pixel values for width and height (e.g., `iconSize: size(16px, 16px);`). Generates `style::size`.
* `point`: Two pixel values for x and y coordinates (e.g., `textPos: point(5px, 2px);`). Generates `style::point`.
* `align`: Alignment keywords (e.g., `textAlign: align(center);` or `iconAlign: align(left);`). Generates `style::align`.
* `font`: Font definitions (e.g., `font: font(14px semibold);`). Generates `style::font`.
* `double`: Floating point numbers (e.g., `disabledOpacity: 0.5;`)
*Note on Borders:* Borders are typically defined using multiple fields like `border: pixels;` (for width) and `borderFg: color;` (for color), rather than a single CSS-like property.
2. **Structure Definition:** You can define complex data structures directly within the `.style` file:
```style
MyButtonStyle { // Defines a structure named 'MyButtonStyle'
textPadding: margins; // Field 'textPadding' expects margins type
icon: icon; // Field 'icon' of type icon
height: pixels; // Field 'height' of type pixels
}
```
This generates a `struct MyButtonStyle { ... };` inside the `namespace style`. Fields will have corresponding C++ types (`style::margins`, `style::icon`, `int`).
3. **Variable Definition & Inheritance:** Variables are defined using `name: value;` or `groupName { ... }`. They can be of built-in types or custom structures. Structures can be initialized inline or inherit from existing variables.
**Icon Definition Syntax:** Icons are defined inline using the `icon{...}` syntax. The generator probes for `.svg` files or `.png` files (including `@2x`, `@3x` variants) based on the provided path stem.
```style
// Single-part icon definition:
myIconSearch: icon{{ "gui/icons/search", iconColor }};
// Multi-part icon definition (layers drawn bottom-up):
myComplexIcon: icon{
{ "gui/icons/background", iconBgColor },
{ "gui/icons/foreground", iconFgColor }
};
// Icon with path modifiers (PNG only for flips, SVG only for size):
myFlippedIcon: icon{{ "gui/icons/arrow-flip_horizontal", arrowColor }};
myResizedIcon: icon{{ "gui/icons/logo-128x128", logoColor }}; // Forces 128x128 for SVG
```
**Other Variable Examples:**
```style
// Simple variables
buttonHeight: 30px;
activeButtonColor: buttonBgActive; // Named color from colors.palette
// Variable of a custom structure type, initialized inline
defaultButton: MyButtonStyle {
textPadding: margins(10px, 15px, 10px, 15px); // Use margins(...) syntax
icon: myIconSearch; // Assign the previously defined icon variable
height: buttonHeight; // Reference another variable
}
// Another variable inheriting from 'defaultButton' and overriding/adding fields
primaryButton: MyButtonStyle(defaultButton) {
icon: myComplexIcon; // Override icon with the multi-part one
backgroundColor: activeButtonColor; // Add a field not in MyButtonStyle definition
}
// Style group (often used for specific UI elements)
chatInput { // Example using separate border properties and explicit padding
border: 1px; // Border width
borderFg: defaultInputFieldBorder; // Border color (named color)
padding: margins(5px, 10px, 5px, 10px); // Use margins(...) syntax for padding field
backgroundColor: defaultChatBg; // Background color
}
```
## Code Generation
A code generation tool processes these `.style` files and `colors.palette` to create C++ objects.
- The `using` directives resolve dependencies between `.style` files.
- Custom structure definitions (like `MyButtonStyle`) generate corresponding `struct MyButtonStyle { ... };` within the `namespace style`.
- Style variables/groups (like `defaultButton`, `primaryButton`, `chatInput`) are generated as objects/structs within the `st` namespace (e.g., `st::defaultButton`, `st::primaryButton`, `st::chatInput`). These generated structs contain members corresponding to the fields defined in the `.style` file.
- Color objects are generated into the `st` namespace as well, based on their names in `colors.palette` (e.g., `st::windowBg`, `st::buttonBgActive`).
- The generated header files for styles are placed in the `Telegram/SourceFiles/styles/` directory with a `style_` prefix (e.g., `styles/style_widgets.h` for `ui/widgets/widgets.style`). You include them like `#include "styles/style_widgets.h"`.
Generated C++ types correspond to the `.style` types: `style::color`, `style::font`, `style::margins` (used for both `margin:` and `padding:` fields), `style::icon`, `style::size`, `style::point`, `style::align`, and `int` or `bool` for simple types.
## Style Usage in Code
Styles are applied in C++ code by referencing the generated `st::...` objects and their members.
```cpp
// Example: Including the generated style header
#include "styles/style_widgets.h" // For styles defined in ui/widgets/widgets.style
// ... inside some UI class code ...
// Accessing members of a generated style struct
int height = st::primaryButton.height; // Accessing the 'height' field (pixels -> int)
const style::icon &icon = st::primaryButton.icon; // Accessing the 'icon' field (st::myComplexIcon)
style::margins padding = st::primaryButton.textPadding; // Accessing 'textPadding'
style::color bgColor = st::primaryButton.backgroundColor; // Accessing the color (st::activeButtonColor)
// Applying styles (conceptual examples)
myButton->setIcon(st::primaryButton.icon);
myButton->setHeight(st::primaryButton.height);
myButton->setPadding(st::primaryButton.textPadding);
myButton->setBackgroundColor(st::primaryButton.backgroundColor);
// Using styles directly in painting
void MyWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(rect(), st::chatInput.backgroundColor); // Use color from chatInput style
// Border painting requires width and color
int borderWidth = st::chatInput.border; // Access border width (pixels -> int)
style::color borderColor = st::chatInput.borderFg; // Access border color
if (borderWidth > 0) {
p.setPen(QPen(borderColor, borderWidth));
// Adjust rect for pen width if needed before drawing
p.drawRect(rect().adjusted(borderWidth / 2, borderWidth / 2, -borderWidth / 2, -borderWidth / 2));
}
// Access padding (style::margins)
style::margins inputPadding = st::chatInput.padding;
// ... use inputPadding.top(), inputPadding.left() etc. for content layout ...
}
```
**Key Points:**
* Styles are defined in `.style` files next to their corresponding C++ source files.
* `using "path/to/other.style";` includes definitions from other style files.
* Named colors are defined centrally in `ui/colors.palette`.
* `.style` syntax supports built-in types (like `pixels`, `color`, `margins`, `point`, `size`, `align`, `font`, `double`), custom structure definitions (`Name { field: type; ... }`), variable definitions (`name: value;`), and inheritance (`child: Name(parent) { ... }`).
* Values must match the expected type (e.g., fields declared as `margins` type, like `margin:` or `padding:`, require `margins(...)` syntax). Borders are typically set via separate `border: pixels;` and `borderFg: color;` fields.
* Icons are defined inline using `name: icon{{ "path_stem", color }};` or `name: icon{ { "path1", c1 }, ... };` syntax, with optional path modifiers.
* Code generation creates `struct` definitions in the `style` namespace for custom types and objects/structs in the `st` namespace for defined variables/groups.
* Generated headers are in `styles/` with a `style_` prefix and must be included.
* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`).