Updated to 6.7.
@@ -1,35 +1,101 @@
|
||||
# Phase Prompts
|
||||
|
||||
Use these templates for `codex exec --json` child runs. Replace `<TASK>`, `<PROJECT>`, `<LETTER>`, and `<REPO_ROOT>`.
|
||||
Use these templates as Codex subagent messages. Use them as same-session checklists only for Phase 0, intentional main-session build work, Phase 7, or when delegation is unavailable from the start. Replace `<TASK>`, `<PROJECT>`, `<LETTER>`, and `<REPO_ROOT>`.
|
||||
|
||||
## Orchestration Rules
|
||||
|
||||
- Phase 0 runs in the main session.
|
||||
- When delegation is available, use a fresh subagent for Phase 1, Phase 2, Phase 3, each Phase 4 implementation unit, and each Phase 6 pass. Do not switch those phases to same-session midstream because of a timeout or missing artifact.
|
||||
- Phase 7 runs in the main session on Windows because it depends on the final local diff and touched-file set.
|
||||
- Write each phase prompt to `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.prompt.md` before execution.
|
||||
- If you delegate a phase, send the prompt file contents as the initial `spawn_agent` message.
|
||||
- When writing the phase prompt file, append the standard progress file contract and the standard compact reply block below so the subagent knows how to surface progress before the final artifact.
|
||||
- After each phase completes, write `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.result.md` summarizing the status, files touched, and any follow-up notes.
|
||||
- Use `fork_context: false` by default. If the phase depends on thread-only context or UI attachments, pass that context explicitly or enable `fork_context` only for that phase.
|
||||
- Prefer `worker` for phases that write files. Use `default` for plan or review passes if that fits the host better. Use `explorer` only for narrow read-only questions.
|
||||
- When supported, request `model: gpt-5.4` and `reasoning_effort: xhigh` for delegated phases.
|
||||
- Default wait budget for delegated phases is 5 minutes while the phase is clearly still in progress. Successful completion may wake earlier, so this does not delay finished work.
|
||||
- When a phase appears close to landing, use 1-2 minute waits until it finishes.
|
||||
- A `wait_agent` timeout is not failure. On timeout, inspect both the expected artifact and the matching progress file before deciding anything.
|
||||
- If the expected artifact exists and shows progress, wait again.
|
||||
- If the expected artifact is not ready but the progress file mtime moved or its heartbeat counter increased since the previous check, wait again. Prefer mtime checks first and avoid rereading the file unless you need detail. Do not count that as a failed wait.
|
||||
- If neither the expected artifact nor the progress file moved since the previous blocked check, send one short follow-up asking the same agent to refresh the progress file, finish the required artifact, and return the standard compact reply block, then wait again.
|
||||
- If the same agent still produces no usable artifact and no meaningful progress-file movement after two full default waits and one follow-up, close it and retry the phase in a fresh subagent.
|
||||
- For Phase 1, Phase 2, Phase 3, Phase 4, and Phase 6, if delegated retries still fail, stop and ask the user rather than rerunning the phase locally.
|
||||
- Never use `codex exec`, background shell child processes, or JSONL child-session logging from this skill.
|
||||
|
||||
## Standard Progress File Contract
|
||||
|
||||
Append this verbatim to every delegated phase prompt:
|
||||
|
||||
```text
|
||||
Before deep work, create or update the matching progress file in `.ai/<PROJECT>/<LETTER>/logs/`.
|
||||
|
||||
Use `<phase-name>.progress.md` as a concise heartbeat with:
|
||||
- `Heartbeat: <N>` on the first line, incremented on each meaningful update
|
||||
- Current step
|
||||
- Files being read or edited
|
||||
- Concrete findings or decisions so far
|
||||
- Blocker or next checkpoint
|
||||
|
||||
Update it sparingly: preferably at natural milestones, and otherwise only after a longer quiet stretch such as roughly 5-10 minutes.
|
||||
Keep it tiny so the parent can usually rely on file mtime or the heartbeat counter instead of rereading the whole file.
|
||||
Do not wait until the final artifact to write progress.
|
||||
```
|
||||
|
||||
## Standard Compact Reply Block
|
||||
|
||||
Append this verbatim to every delegated phase prompt:
|
||||
|
||||
```text
|
||||
Before replying in chat, write the required artifact(s) to disk.
|
||||
|
||||
Reply in 8 lines or fewer using exactly these keys:
|
||||
STATUS: <DONE|BLOCKED|APPROVED|NEEDS_CHANGES>
|
||||
ARTIFACTS: <paths>
|
||||
TOUCHED: <repo paths or none>
|
||||
BLOCKER: <none or one short line>
|
||||
|
||||
Do not restate the full context, plan, diff, or long reasoning in the chat reply.
|
||||
```
|
||||
|
||||
## Artifact-Based Completion Checks
|
||||
|
||||
- Phase 1 is complete only when `about.md` and `context.md` both exist and are non-empty.
|
||||
- Phase 2 is complete only when `plan.md` exists, contains a `## Status` section, and no unintended source edits were made.
|
||||
- Phase 3 is complete only when `plan.md` contains both `Phases:` in the Status section and `Assessed: yes`.
|
||||
- Phase 4 is complete only when the target phase checkbox changed to checked and the touched-file list matches the owned write set, or the blocker explains any mismatch.
|
||||
- Phase 5 is complete only when the build outcome is known and the build checkbox is updated on success.
|
||||
- Phase 6a is complete only when `review<R>.md` exists and contains a verdict line.
|
||||
- Phase 6b is complete only when the requested fixes were applied and the post-fix build outcome is known.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
Before running any phase prompts, 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.
|
||||
Follow-up detection:
|
||||
1. Extract the first word or token from the task description. Call it `FIRST_TOKEN`.
|
||||
2. Check `.ai/` to see existing project names.
|
||||
3. Check whether `.ai/<FIRST_TOKEN>/about.md` exists.
|
||||
4. If the file exists, this is a follow-up task. The project name is `FIRST_TOKEN`. The task description is everything after `FIRST_TOKEN`.
|
||||
5. If the file does not exist, 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.**
|
||||
Do not proceed until you have 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/`.
|
||||
For new projects:
|
||||
- Using the list of existing projects, pick a unique short name (1-2 lowercase words, hyphen-separated) that does not collide.
|
||||
- Create `.ai/<PROJECT>/`, `.ai/<PROJECT>/a/`, and `.ai/<PROJECT>/a/logs/`.
|
||||
- Set `<LETTER>` = `a`.
|
||||
|
||||
**For follow-up tasks:**
|
||||
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/`.
|
||||
- Create `.ai/<PROJECT>/<LETTER>/` and `.ai/<PROJECT>/<LETTER>/logs/`.
|
||||
|
||||
Then proceed to Phase 1. Follow-up tasks do NOT skip context gathering — they go through a modified version of it.
|
||||
Then proceed to Phase 1. Follow-up tasks do not skip context gathering. They use a modified Phase 1F prompt.
|
||||
|
||||
## Phase 1: Context (New Project, letter = `a`)
|
||||
|
||||
@@ -38,50 +104,50 @@ 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.
|
||||
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.
|
||||
3. Read all potentially relevant files. Be thorough and prefer reading 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
|
||||
- file path
|
||||
- relevant line ranges
|
||||
- what the code does and how it relates to the task
|
||||
- key data structures, function signatures, and 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:
|
||||
Write two files.
|
||||
|
||||
### File 1: .ai/<PROJECT>/about.md
|
||||
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.
|
||||
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 phase should rely on it during the current 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
|
||||
- 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.
|
||||
Do not include temporal state like "Current State", "Pending Changes", "Not yet implemented", or "TODO". Describe the project as a complete, coherent whole.
|
||||
|
||||
### File 2: .ai/<PROJECT>/a/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
|
||||
This is the primary task-specific implementation context. All downstream phases should be able to work from this file plus the referenced source files. It must be self-contained. Include:
|
||||
- Task Description: The full task restated clearly
|
||||
- Relevant Files: Every file path with line ranges and descriptions
|
||||
- Key Code Patterns: How similar things are done in the codebase, with snippets when useful
|
||||
- Data Structures: Relevant types, structs, classes
|
||||
- API Methods: Any TL schema methods involved, copied from api.tl when useful
|
||||
- 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.
|
||||
Be extremely thorough. Another agent with no prior context will rely on this file.
|
||||
|
||||
Do not implement code in this phase.
|
||||
```
|
||||
@@ -97,49 +163,44 @@ YOUR JOB: Read the existing project state, gather any additional context needed,
|
||||
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
Write two files.
|
||||
|
||||
### File 1: .ai/<PROJECT>/about.md (REWRITE)
|
||||
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.
|
||||
Rewrite this file instead of appending to it. 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
|
||||
- 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 a pending change
|
||||
- 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)
|
||||
It should not contain:
|
||||
- temporal state such as "Current State", "Pending Changes", or "TODO"
|
||||
- history of how requirements changed between tasks
|
||||
- references to "the old approach" versus "the new approach"
|
||||
- task-by-task changelog or timeline
|
||||
- information that contradicts the new task requirements
|
||||
|
||||
### File 2: .ai/<PROJECT>/<LETTER>/context.md
|
||||
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.
|
||||
This is the primary document for the new task. It must be self-contained and should include:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
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.
|
||||
Be extremely thorough. Another agent with no prior context should be able to work from this file alone.
|
||||
|
||||
Do not implement code in this phase.
|
||||
```
|
||||
@@ -150,7 +211,7 @@ Do not implement code in this phase.
|
||||
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
|
||||
- .ai/<PROJECT>/<LETTER>/context.md
|
||||
- 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
|
||||
@@ -172,24 +233,23 @@ The plan.md should contain:
|
||||
## 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.)
|
||||
- exact file paths
|
||||
- exact function names
|
||||
- what code to add, modify, or remove
|
||||
- where exactly in the file (after which function, in which class, and so on)
|
||||
|
||||
Number every step. Group steps into phases if there are more than ~8 steps.
|
||||
Number every step. Group steps into phases if there are more than about eight steps.
|
||||
|
||||
### Phase 1: <name>
|
||||
1. <specific step>
|
||||
2. <specific step>
|
||||
...
|
||||
|
||||
### Phase 2: <name> (if needed)
|
||||
...
|
||||
1. <specific step>
|
||||
|
||||
## Build Verification
|
||||
- Build command to run
|
||||
- Expected outcome
|
||||
- build command to run
|
||||
- expected outcome
|
||||
|
||||
## Status
|
||||
- [ ] Phase 1: <name>
|
||||
@@ -212,59 +272,68 @@ Read these files:
|
||||
|
||||
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.
|
||||
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 about 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
|
||||
- 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>`
|
||||
- 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.
|
||||
If the plan is small enough for a single agent (roughly 8 steps or fewer), 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:
|
||||
Run one implementation unit per plan phase. Keep implementation phases sequential by default. Parallelize only if their write sets are disjoint and the plan makes that safe.
|
||||
|
||||
For each phase in the plan that is not yet marked as done, use this prompt:
|
||||
|
||||
```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
|
||||
- .ai/<PROJECT>/<LETTER>/context.md
|
||||
- .ai/<PROJECT>/<LETTER>/plan.md
|
||||
|
||||
Then read the source files you'll be modifying.
|
||||
Then read the source files you will be modifying.
|
||||
|
||||
YOUR TASK: Implement ONLY Phase <N> from the plan:
|
||||
Your owned write set for this phase:
|
||||
<OWNED_WRITE_SET>
|
||||
|
||||
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
|
||||
- Follow the plan precisely.
|
||||
- Follow AGENTS.md coding conventions.
|
||||
- You are not alone in the codebase. Respect existing changes and do not revert unrelated work.
|
||||
- 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
|
||||
- Do not work on other phases.
|
||||
|
||||
When finished, report what you did and any issues encountered.
|
||||
When finished, report what you did, which files you changed, 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.
|
||||
After each implementation phase:
|
||||
1. Use a narrow read or search to confirm the status line was updated.
|
||||
2. Verify the owned write set and touched files with a small diff summary such as `git diff --name-only`.
|
||||
3. If more phases remain, run the next implementation phase.
|
||||
4. 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).
|
||||
Only run this phase if the task modified project source code.
|
||||
|
||||
Prefer running the build in the main session because it is critical-path work. If you delegate it, use a worker subagent and wait immediately for the result.
|
||||
|
||||
```text
|
||||
You are a build verification agent.
|
||||
@@ -273,7 +342,7 @@ 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.
|
||||
The implementation is complete. Your job is to build the project and fix any build errors that block the planned work.
|
||||
|
||||
Steps:
|
||||
1. Run (from repository root): cmake --build ./out --config Debug --target Telegram
|
||||
@@ -286,76 +355,66 @@ Steps:
|
||||
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
|
||||
- Only fix build errors. Do not refactor or improve code beyond what is needed for a passing build.
|
||||
- Follow AGENTS.md conventions.
|
||||
- If build fails with file-locked errors (C1041, LNK1104, "cannot open output file", or similar access-denied lock issues), stop and report the lock. Do not retry.
|
||||
- You are not alone in the codebase. Respect existing changes and do not revert unrelated work.
|
||||
|
||||
When finished, report the build result.
|
||||
When finished, report the build result and which files, if any, you changed.
|
||||
```
|
||||
|
||||
## 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`.
|
||||
After build verification passes, run up to 3 review-fix iterations. Set iteration counter `R = 1`.
|
||||
|
||||
### Review Loop
|
||||
Review loop:
|
||||
|
||||
```
|
||||
```text
|
||||
LOOP:
|
||||
1. Run review agent (Step 6a) with iteration R
|
||||
1. Run review phase 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:
|
||||
- "APPROVED" -> go to FINISH
|
||||
- "NEEDS_CHANGES" -> run fix phase 6b
|
||||
3. After fix work completes and build passes:
|
||||
R = R + 1
|
||||
If R > 3 → go to FINISH (stop iterating, accept current state)
|
||||
Otherwise → go to step 1
|
||||
If R > 3 -> go to FINISH
|
||||
Otherwise -> go to step 1
|
||||
|
||||
FINISH:
|
||||
- Update plan.md: change `- [ ] Code review` to `- [x] Code review`
|
||||
- Proceed to Completion
|
||||
- Proceed to Phase 7 on Windows, otherwise proceed to Completion
|
||||
```
|
||||
|
||||
### Step 6a: Code Review Agent
|
||||
### Step 6a: Code Review
|
||||
|
||||
```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)
|
||||
- .ai/<PROJECT>/<LETTER>/context.md
|
||||
- .ai/<PROJECT>/<LETTER>/plan.md
|
||||
- REVIEW.md
|
||||
- If R > 1, also read .ai/<PROJECT>/<LETTER>/review<R-1>.md
|
||||
|
||||
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 run `git diff` to see the current uncommitted changes for this task.
|
||||
|
||||
Then read the modified source files in full to understand changes in context.
|
||||
Read the modified source files in full to understand the changes in context.
|
||||
|
||||
Perform a thorough code review.
|
||||
Perform a focused code review using these criteria, in order:
|
||||
|
||||
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.
|
||||
2. Dead code: Added or left-behind code that is never used within the scope of the changes.
|
||||
3. Redundant changes: Diff hunks that have no functional effect.
|
||||
4. Code duplication: Repeated logic that should be shared.
|
||||
5. Wrong placement: Code added to a module where it does not logically belong.
|
||||
6. Function decomposition: Whether an extracted helper would clearly improve readability.
|
||||
7. Module structure: Only in exceptional cases where a large new chunk of code clearly belongs elsewhere.
|
||||
8. Style compliance: REVIEW.md rules and AGENTS.md conventions.
|
||||
|
||||
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.
|
||||
Important guidelines:
|
||||
- Review only the changes made, not pre-existing code outside the scope of the task.
|
||||
- Be pragmatic. Each suggestion should have a clear, concrete benefit.
|
||||
- Do not suggest comments, docstrings, or over-engineering.
|
||||
|
||||
Write your review to: .ai/<PROJECT>/<LETTER>/review<R>.md
|
||||
|
||||
@@ -368,87 +427,133 @@ The review document should contain:
|
||||
|
||||
## Verdict: <APPROVED or NEEDS_CHANGES>
|
||||
|
||||
<If APPROVED, stop here. Everything looks good.>
|
||||
|
||||
<If NEEDS_CHANGES, continue with:>
|
||||
If the verdict is 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>
|
||||
- Category: <dead code | duplication | wrong placement | function decomposition | module structure | style | correctness>
|
||||
- File(s): <file paths>
|
||||
- Problem: <clear description>
|
||||
- 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.
|
||||
Keep the list focused. Prioritize the most impactful issues.
|
||||
|
||||
When finished, report your verdict clearly as: APPROVED or NEEDS_CHANGES.
|
||||
```
|
||||
|
||||
### Step 6b: Review Fix Agent
|
||||
### Step 6b: Review Fix
|
||||
|
||||
```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
|
||||
- .ai/<PROJECT>/<LETTER>/context.md
|
||||
- .ai/<PROJECT>/<LETTER>/plan.md
|
||||
- .ai/<PROJECT>/<LETTER>/review<R>.md
|
||||
|
||||
Then read the source files mentioned in the review.
|
||||
|
||||
YOUR TASK: Implement ALL changes listed in review<R>.md.
|
||||
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.
|
||||
Rules:
|
||||
- Implement exactly the review changes, nothing more.
|
||||
- Follow AGENTS.md coding conventions.
|
||||
- You are not alone in the codebase. Respect existing changes and do not revert unrelated work.
|
||||
- Do not modify .ai/ files except where the review process explicitly requires it.
|
||||
|
||||
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.
|
||||
3. If build fails with file-locked errors (C1041, LNK1104, "cannot open output file", or similar access-denied lock issues), stop and report the lock. Do not retry.
|
||||
|
||||
When finished, report what changes were made and which files you touched.
|
||||
```
|
||||
|
||||
## Phase 7: Windows Text Normalization
|
||||
|
||||
Run this phase only on Windows hosts and only after the review loop has finished.
|
||||
|
||||
Use the current task's result logs as the source of truth for what Codex touched. Do not sweep the whole repo and do not rewrite unrelated files from a dirty worktree.
|
||||
|
||||
```text
|
||||
You are performing the final Windows-only text normalization phase for task-think.
|
||||
|
||||
Read these files:
|
||||
- .ai/<PROJECT>/<LETTER>/plan.md
|
||||
- .ai/<PROJECT>/<LETTER>/logs/phase-4*.result.md
|
||||
- .ai/<PROJECT>/<LETTER>/logs/phase-5*.result.md
|
||||
- .ai/<PROJECT>/<LETTER>/logs/phase-6*.result.md
|
||||
|
||||
Your job:
|
||||
- Collect the union of repo file paths listed under "Touched files" in those result logs.
|
||||
- Keep only files inside the repository that currently exist and are textual project files: source, headers, build/config files, localization files, style files, and similar text assets.
|
||||
- Exclude `.ai/`, `out/`, binary files, and unrelated user files that were not touched by Codex in this task.
|
||||
- Rewrite each kept file so all line endings are CRLF.
|
||||
- If a kept file is UTF-8 or ASCII text, write it back as UTF-8 without BOM. Never add a UTF-8 BOM to source/config/project text files.
|
||||
- Preserve file content otherwise. Preserve whether the file ended with a trailing newline.
|
||||
|
||||
Rules:
|
||||
- Implement exactly the changes from the review, nothing more.
|
||||
- Follow AGENTS.md coding conventions.
|
||||
- Do NOT modify .ai/ files.
|
||||
- Run this phase in the main session on Windows.
|
||||
- Do not modify files outside the touched-file set for the current task.
|
||||
- Do not rewrite binary files.
|
||||
- When scripting this phase, do not use writer APIs or defaults that emit UTF-8 with BOM.
|
||||
- If a file cannot be normalized safely, record it as a failure instead of silently skipping it.
|
||||
|
||||
When finished, report what changes were made.
|
||||
When finished:
|
||||
1. Write `.ai/<PROJECT>/<LETTER>/logs/phase-7-line-endings.result.md`
|
||||
2. Include:
|
||||
- whether the phase completed
|
||||
- which files were normalized
|
||||
- which files were skipped and why
|
||||
- whether any UTF-8 BOMs were removed or verified absent
|
||||
- any failures that need to be mentioned in the final summary
|
||||
```
|
||||
|
||||
## Completion
|
||||
|
||||
When all phases including build verification and code review are done:
|
||||
When all phases, including build verification, code review, and Windows line ending normalization when applicable, are done:
|
||||
1. Read the final `plan.md` and report the summary to the user.
|
||||
2. Show which files were modified/created.
|
||||
2. Show which files were modified or 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.
|
||||
4. Summarize the code review iterations: how many rounds, what was found and fixed, or whether it was approved on the first pass.
|
||||
5. On Windows, mention the text-normalization result briefly: which project files were normalized, whether any BOMs were removed, or whether nothing needed changes.
|
||||
6. Calculate and display the total elapsed time since `$START_TIME` (format as `Xh Ym Zs`, omitting zero components).
|
||||
7. 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 any phase fails or gets stuck, follow the timeout and retry rules above. Do not close an agent solely because the final artifact is missing while its progress file is still advancing. For Phase 1, Phase 2, Phase 3, Phase 4, and Phase 6, do not rerun locally after delegated retries fail; ask the user instead.
|
||||
- If `context.md` or `plan.md` is not written properly by a phase, rerun that phase in a fresh subagent 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.
|
||||
- If a review-fix phase introduces new build errors that it cannot resolve, report to the user.
|
||||
|
||||
## Reasoning Effort
|
||||
## Prompt Delivery And Logs
|
||||
|
||||
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.
|
||||
For each phase:
|
||||
1. Write the full prompt to `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.prompt.md`
|
||||
2. Delegate by sending that prompt text to a fresh subagent, or use it as a same-session checklist only for the designated main-session phases or when delegation was unavailable from the start
|
||||
3. For delegated phases, expect a matching `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.progress.md` heartbeat while work is in flight
|
||||
4. Save a concise completion note to `.ai/<PROJECT>/<LETTER>/logs/phase-<name>.result.md`
|
||||
|
||||
## Example Runner Commands
|
||||
For review iterations, include the iteration in the file name, for example:
|
||||
- `phase-6a-review-1.prompt.md`
|
||||
- `phase-6a-review-1.result.md`
|
||||
- `phase-6b-fix-1.prompt.md`
|
||||
- `phase-6b-fix-1.result.md`
|
||||
|
||||
```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
|
||||
```
|
||||
## Subagent Pattern
|
||||
|
||||
Use this pattern conceptually for delegated phases:
|
||||
|
||||
1. Write the phase prompt file.
|
||||
2. Spawn a fresh subagent with the phase prompt, usually with `fork_context: false`.
|
||||
3. Require the agent to create the matching progress file early and refresh it sparingly: at natural milestones when possible, otherwise only after a longer quiet stretch such as roughly 5-10 minutes.
|
||||
4. Wait in 5-minute intervals when the next step is blocked on that phase, checking both the final artifact and the progress file on timeout.
|
||||
5. When the phase looks close to finishing, switch to 1-2 minute waits.
|
||||
6. Prefer filesystem mtime checks on the progress file first. If its mtime moved or the heartbeat counter increased, keep waiting; do not treat that as a stall.
|
||||
7. If neither the artifact nor the progress file moves, send one short follow-up to the same agent, then retry once with a fresh subagent before involving the user.
|
||||
8. Validate the expected artifact or code changes with small shell summaries and the completion checks above.
|
||||
9. Write the result log from the validated outcome and the compact reply block.
|
||||
|
||||
Do not replace this pattern with shell-launched `codex exec`.
|
||||
|
||||
@@ -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 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.
|
||||
description: Orchestrate a multi-phase implementation workflow for this repository with artifact files under .ai/<project-name>/<letter>/ using Codex subagents instead of shell-spawned child processes. Use when the user wants one prompt to drive context gathering, planning, plan assessment, implementation, build verification, and review with persistent artifacts, clear phase handoffs, and a thin parent thread. Prefer spawn_agent/send_input/wait_agent, keep heavy pre-build work delegated when possible, and avoid pulling timed-out phases back into the main session.
|
||||
---
|
||||
|
||||
# Task Pipeline
|
||||
@@ -15,14 +15,14 @@ Collect:
|
||||
- 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.
|
||||
If screenshots are attached in UI but not present as files, write a brief textual summary into the task artifacts before spawning fresh subagents so later phases can read the requirements without inheriting the whole parent thread.
|
||||
|
||||
## Overview
|
||||
|
||||
The workflow is organized around **projects**. Each project lives in `.ai/<project-name>/` and can contain multiple sequential **tasks** (labeled `a`, `b`, `c`, ... `z`).
|
||||
The workflow is organized around projects. Each project lives in `.ai/<project-name>/` and can contain multiple sequential tasks (labeled `a`, `b`, `c`, ... `z`).
|
||||
|
||||
Project structure:
|
||||
```
|
||||
```text
|
||||
.ai/<project-name>/
|
||||
about.md # Single source of truth for the entire project
|
||||
a/ # First task
|
||||
@@ -31,16 +31,22 @@ Project structure:
|
||||
review1.md # Code review documents (up to 3 iterations)
|
||||
review2.md
|
||||
review3.md
|
||||
logs/
|
||||
phase-*.prompt.md
|
||||
phase-*.progress.md
|
||||
phase-*.result.md
|
||||
b/ # Follow-up task
|
||||
context.md
|
||||
plan.md
|
||||
review1.md
|
||||
logs/
|
||||
...
|
||||
c/ # Another follow-up task
|
||||
...
|
||||
```
|
||||
|
||||
- `about.md` is the project-level blueprint — a single comprehensive document describing what this project does and how it works, written as if everything is already fully implemented. It contains no temporal state ("current state", "pending changes", "not yet implemented"). It is **rewritten** (not appended to) each time a new task starts, incorporating the new task's changes as if they were always part of the design.
|
||||
- Each task folder (`a/`, `b/`, ...) contains self-contained files for that task. The task's `context.md` carries all task-specific information: what specifically needs to change, the delta from the current codebase, gathered file references and code patterns. Planning, implementation, and review agents only read the current task's folder.
|
||||
- `about.md` is the project-level blueprint: a single comprehensive document describing what this project does and how it works, written as if everything is already fully implemented. It contains no temporal state ("current state", "pending changes", "not yet implemented"). It is rewritten, not appended to, each time a new task starts, incorporating the new task's changes as if they were always part of the design.
|
||||
- Each task folder (`a/`, `b/`, ...) contains self-contained files for that task. The task's `context.md` carries all task-specific information: what specifically needs to change, the delta from the current codebase, gathered file references, and code patterns. Planning, implementation, and review phases should rely on the current task folder.
|
||||
|
||||
## Artifacts
|
||||
|
||||
@@ -49,38 +55,64 @@ Create and maintain:
|
||||
- `.ai/<project-name>/<letter>/context.md`
|
||||
- `.ai/<project-name>/<letter>/plan.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`)
|
||||
- `.ai/<project-name>/<letter>/logs/phase-<name>.prompt.md`
|
||||
- `.ai/<project-name>/<letter>/logs/phase-<name>.progress.md` for delegated phases
|
||||
- `.ai/<project-name>/<letter>/logs/phase-<name>.result.md`
|
||||
|
||||
Each `phase-<name>.result.md` should capture a concise outcome summary: whether the phase completed, which files it touched, and any follow-up notes or blockers.
|
||||
Each delegated `phase-<name>.progress.md` should act as a heartbeat: a tiny monotonic counter plus current step, files being read or edited, concrete findings so far, and the next checkpoint. It is not a final artifact; it exists so the parent can distinguish active research from a truly stuck subagent without rereading large context.
|
||||
|
||||
## Phases
|
||||
|
||||
The workflow runs these phases sequentially via `codex exec --json` child sessions:
|
||||
Run these phases sequentially:
|
||||
|
||||
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.
|
||||
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`. Use Phase 1F for follow-up tasks.
|
||||
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 - Execute one implementation unit per plan phase.
|
||||
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 - Run review and fix iterations until approved or the iteration limit is reached.
|
||||
8. Phase 7: Windows Text Normalization - On Windows only, after review passes and before the final summary, normalize LF to CRLF for the text source/config files Codex edited in this task and ensure rewritten UTF-8 project files are saved without BOM.
|
||||
|
||||
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.
|
||||
Use Codex subagents as the primary orchestration mechanism.
|
||||
|
||||
Phases that require elevated reasoning (Planning, Plan Assessment, Code Review) must use `-c model_reasoning_effort="xhigh"`. See example commands in `PROMPTS.md`.
|
||||
- When delegation is available, Phase 1, Phase 2, Phase 3, each Phase 4 implementation unit, and each Phase 6 review or review-fix pass must run in fresh subagents. Do not rerun those phases in the main session midstream just because a wait timed out or an artifact is missing.
|
||||
- Run Phase 7 in the main session on Windows because it depends on the final local file state and the exact touched-file set for the current task.
|
||||
- When any same-session helper rewrites Windows project text files, preserve CRLF and write UTF-8 without BOM. Avoid writer APIs or defaults that silently inject a UTF-8 BOM.
|
||||
- The main session may read `context.md` once after Phase 1 and `plan.md` once after Phase 3. After that, prefer narrow shell checks, file existence checks, and status-line reads instead of rereading full documents or diffs.
|
||||
- Prefer `worker` for phases that write files. Use `explorer` only for narrow read-only questions that unblock your next local step.
|
||||
- Keep `fork_context` off by default. Pass the phase prompt and explicit file paths instead of the whole thread unless the phase truly needs prior conversational context or thread-only attachments.
|
||||
- When the platform supports it, request `model: gpt-5.4` and `reasoning_effort: xhigh` for spawned phase agents. If overrides are unavailable, inherit the current session settings.
|
||||
- Write the exact phase prompt to the matching `logs/phase-<name>.prompt.md` file before you delegate. Use the same prompt file as a checklist if you later need to fall back to same-session execution.
|
||||
- For delegated phases, require an early `logs/phase-<name>.progress.md` heartbeat before deep work. The subagent should create or update it early, keep it tiny, and refresh it sparingly: preferably at natural milestones, and otherwise only after a longer quiet stretch such as roughly 5-10 minutes.
|
||||
- In every delegated prompt, require a compact final reply with only status, artifact paths, touched files, and blocker or `none`. Detailed reasoning belongs in `.ai/` artifacts, not in the chat reply.
|
||||
- After a subagent finishes, verify that the expected artifacts or code changes exist, then write a short result log in `logs/phase-<name>.result.md`.
|
||||
- For delegated phases, use `wait_agent` with a 5-minute timeout by default while a phase is still clearly in progress. Successful completion may wake earlier, so this does not add latency to finished phases.
|
||||
- When a phase looks close to completion — for example the final artifact has appeared, a build is in its final pass, or the agent said it is wrapping up — switch to 1-2 minute waits until it lands.
|
||||
- A timeout is not a failure; it only means no final status arrived yet. Do not treat short waits as stall detection for research-heavy phases.
|
||||
- On timeout, inspect the expected artifact, the phase progress file mtime, and the worktree for movement. Prefer mtime checks first; only reread the progress file when you need detail.
|
||||
- If the progress file mtime moved or its heartbeat counter increased since the previous check, treat that as active progress and wait again.
|
||||
- If no usable final artifact exists yet but the progress file is appearing or advancing, keep the same subagent alive. Progress-file movement does not count toward the retry limit.
|
||||
- If no usable final artifact exists yet and neither the expected artifact nor the progress file has moved since the previous blocked check, send one short follow-up asking the same subagent to refresh the progress file, finish the artifact, and return the compact status block, then wait again.
|
||||
- Only if the same subagent still shows no meaningful movement in either the expected artifact or the progress file after two full default waits and one follow-up should you close it and rerun that phase in a fresh subagent.
|
||||
- Use `wait_agent` only when the next step is blocked on the result. While the delegated phase runs, do small non-overlapping local tasks such as validating directory structure or preparing the next prompt file.
|
||||
- Build verification is critical-path work. Prefer running the build in the main session, and only delegate a bounded build-fix phase when there is a concrete reason.
|
||||
- If subagents are unavailable in the current environment, or current policy does not allow delegation from the start, run the phase in the main session using the same prompt files. Otherwise, do not switch a pre-build phase to same-session midstream. Never fall back to shell-spawned `codex exec` child processes from this skill.
|
||||
|
||||
## Verification Rules
|
||||
|
||||
- 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.
|
||||
- Treat a delegated phase as complete only when the required artifact or status update exists on disk and matches the phase goals; do not rely on the chat reply alone.
|
||||
- Never claim completion without:
|
||||
- implemented code changes present
|
||||
- build attempt results recorded
|
||||
- review pass documented with any follow-up fixes
|
||||
- on Windows, if the task edited project source/config text files, a CRLF / no-BOM normalization pass recorded after review
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
@@ -88,24 +120,26 @@ Mark complete only when:
|
||||
- All plan phases are done
|
||||
- Build verification is recorded
|
||||
- Review issues are addressed or explicitly deferred with rationale
|
||||
- On Windows, Codex-edited project source/config text files have been normalized to CRLF, any UTF-8 rewrites were saved without BOM, and the result is logged
|
||||
- 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 any phase fails, times out, or gets stuck, follow the retry ladder from Execution Mode. Do not close an agent solely because the final artifact is missing while its progress file is still moving. After two delegated attempts remain blocked with no meaningful progress, report the issue to the user. Do not absorb the phase into the main session before build unless delegation was unavailable from the start.
|
||||
- If `context.md` or `plan.md` is not written properly by a phase, rerun that phase in a fresh subagent with more specific instructions. Do not repair it locally before build unless delegation was unavailable from the start.
|
||||
- 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.
|
||||
- If a review-fix phase introduces new build errors that it cannot resolve, report to the user.
|
||||
- If Phase 7 cannot safely normalize a touched file on Windows or remove an introduced UTF-8 BOM from a touched project text file, record the failure in the result log and report it in the final summary instead of silently skipping it.
|
||||
|
||||
## User Invocation
|
||||
|
||||
Use plain language with the skill name in the request, for example:
|
||||
|
||||
`Use local task-think skill: make sure FileLoadTask::process does not create or read QPixmap on background threads; use QImage with ARGB32_Premultiplied instead.`
|
||||
`Use local task-think skill with subagents: make sure FileLoadTask::process does not create or read QPixmap on background threads; use QImage with ARGB32_Premultiplied instead.`
|
||||
|
||||
For follow-up tasks on an existing project:
|
||||
|
||||
`Use local task-think skill: my-project also handle the case where the file is already cached`
|
||||
`Use local task-think skill with subagents: my-project also handle the case where the file is already cached`
|
||||
|
||||
If screenshots are relevant, include file paths in the same prompt.
|
||||
If screenshots are relevant, include file paths in the same prompt when possible.
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
---
|
||||
description: Generate an SVG icon from a design mockup using vectosolve vectorization
|
||||
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion, TodoWrite, mcp__vectosolve__vectorize
|
||||
---
|
||||
|
||||
# Icon - SVG Icon Generation from Design Mockup
|
||||
|
||||
You generate production-quality SVG icons for Telegram Desktop by vectorizing design mockup screenshots using the vectosolve MCP service, then post-processing the result to match the Telegram icon format.
|
||||
|
||||
**Arguments:** `$ARGUMENTS` = "$ARGUMENTS"
|
||||
|
||||
If `$ARGUMENTS` is empty, ask the user to describe the icon they want and paste a cropped screenshot of it.
|
||||
|
||||
## Overview
|
||||
|
||||
The workflow takes a cropped screenshot of an icon from a design mockup (grabbed from the clipboard), vectorizes it via the vectosolve MCP, then post-processes the SVG (recolor to white-on-transparent, restructure to minimal format, set 24x24 output size).
|
||||
|
||||
Working directory: `.ai/icon_{name}/` with iterations labeled by letter (`a/`, `b/`, ...), each containing `source.png`. Output SVGs are in the icon root: `a.svg`, `b.svg`, etc.
|
||||
|
||||
Follow-ups are supported: `/icon {icon_name} <description>` continues from where the previous run left off.
|
||||
|
||||
## Phase 0: Setup
|
||||
|
||||
**Record the current time** (using `date` or equivalent) as `$START_TIME`.
|
||||
|
||||
### Step 0a: Clipboard grab (MUST be the VERY FIRST action)
|
||||
|
||||
If there is an image attached to the user's message:
|
||||
|
||||
1. Generate a random 8-character hex string for `HASH` (use `openssl rand -hex 4` or similar).
|
||||
2. **IMMEDIATELY** — before any other processing — run this Bash command to save the clipboard image:
|
||||
```bash
|
||||
HASH=$(openssl rand -hex 4) && if [[ "$OSTYPE" == darwin* ]]; then bash .claude/grab_clipboard.sh ".ai/icon_${HASH}.png"; else powershell -ExecutionPolicy Bypass -File .claude/grab_clipboard.ps1 ".ai/icon_${HASH}.png"; fi
|
||||
```
|
||||
On macOS `.claude/grab_clipboard.sh` is used; on Windows `.claude/grab_clipboard.ps1`. Both grab the current clipboard image and save it to the specified path.
|
||||
|
||||
3. If the command fails (exit 1 / no image on clipboard):
|
||||
- Tell the user: **"Clipboard doesn't contain an image. Please copy the icon area first, then retry."** (On macOS: Cmd+Ctrl+Shift+4 to snip to clipboard; on Windows: Win+Shift+S.)
|
||||
- **STOP IMMEDIATELY. Do NOT continue.** You cannot use the image pasted in the conversation — it exists only as pixels in the chat, not as a file you can send to vectosolve. The clipboard grab is the ONLY way to get the image to disk. Do not attempt any workaround.
|
||||
|
||||
4. Read back the saved `.ai/icon_HASH.png` using the Read tool.
|
||||
5. Compare it visually with the image pasted in the conversation. They should depict the same thing.
|
||||
- If they look **completely different**: delete `.ai/icon_HASH.png` and fail:
|
||||
> "The clipboard image doesn't match what you pasted. Please re-copy and retry."
|
||||
- If they look the same (or close enough): proceed. Store the temp path.
|
||||
|
||||
If NO image is attached to the message, skip this step entirely.
|
||||
|
||||
### Step 0b: Fail-fast — verify vectosolve MCP
|
||||
|
||||
Check that the `mcp__vectosolve__vectorize` tool is available by looking at your available tools list. If it is NOT available, fail immediately with:
|
||||
|
||||
> vectosolve MCP is not configured. Set it up with:
|
||||
> ```
|
||||
> claude mcp add vectosolve --scope user -e VECTOSOLVE_API_KEY=vs_xxx -- npx @vectosolve/mcp
|
||||
> ```
|
||||
> Then restart Claude Code.
|
||||
|
||||
### Step 0c: Follow-up detection
|
||||
|
||||
Extract the first word/token from `$ARGUMENTS` (everything before the first space or newline). Call it `FIRST_TOKEN`.
|
||||
|
||||
Run these TWO commands using the Bash tool, **IN PARALLEL**:
|
||||
1. `ls .ai/` — to see all existing icon project names
|
||||
2. `ls .ai/icon_{FIRST_TOKEN}/context.md` — to check if this specific icon project exists
|
||||
|
||||
**Evaluate the results:**
|
||||
- If command 2 **succeeds** (context.md exists): this is a **follow-up**. The icon name is `FIRST_TOKEN`. The follow-up description is everything in `$ARGUMENTS` after `FIRST_TOKEN`.
|
||||
- If command 2 **fails** (not found): this is a **new icon**. The full `$ARGUMENTS` is the icon description.
|
||||
|
||||
### Step 0d: New icon setup
|
||||
|
||||
1. Parse `$ARGUMENTS` to determine:
|
||||
- **Icon description**: what the icon should depict
|
||||
- **Icon type**: default is `menu` (24x24 menu/button icon). User may specify otherwise.
|
||||
- **Target subfolder**: `menu/` by default, or another subfolder if specified.
|
||||
|
||||
2. Choose an icon file name:
|
||||
- Lowercase letters and underscores only — **NO hyphens**
|
||||
- Match existing naming conventions (check `Telegram/Resources/icons/{subfolder}/`)
|
||||
- Must NOT conflict with existing icons
|
||||
- Must NOT collide with existing `.ai/icon_{name}/` directories
|
||||
|
||||
3. Create `.ai/icon_{name}/` and `.ai/icon_{name}/a/`.
|
||||
|
||||
4. Write `.ai/icon_{name}/context.md` with:
|
||||
```
|
||||
## Icon: {icon_name}
|
||||
Type: {menu/other}
|
||||
Target: Telegram/Resources/icons/{subfolder}/{icon_name}.svg
|
||||
|
||||
## Original Request
|
||||
{full $ARGUMENTS text}
|
||||
|
||||
## Follow-ups
|
||||
(none yet)
|
||||
```
|
||||
|
||||
5. Set `LETTER` to `a`.
|
||||
|
||||
### Step 0e: Follow-up setup
|
||||
|
||||
1. Read `.ai/icon_{name}/context.md` to get the icon type, subfolder, and full history.
|
||||
2. Find the latest existing letter folder in `.ai/icon_{name}/` (highest letter).
|
||||
3. Set `LETTER` to the next letter after the latest.
|
||||
4. Create `.ai/icon_{name}/{LETTER}/`.
|
||||
5. Update `.ai/icon_{name}/context.md` — append the follow-up description to the `## Follow-ups` section:
|
||||
```
|
||||
### Follow-up (starting at letter {LETTER})
|
||||
{follow-up description}
|
||||
```
|
||||
|
||||
### Step 0f: Place source image
|
||||
|
||||
If a clipboard image was grabbed in Step 0a:
|
||||
1. Copy (or move) `.ai/icon_HASH.png` → `.ai/icon_{name}/source.png` (overwrite if exists — this is always the latest source).
|
||||
2. Copy it to `.ai/icon_{name}/{LETTER}/source.png` (archive per-iteration source).
|
||||
3. Delete the temp `.ai/icon_HASH.png` if it was copied (not moved).
|
||||
|
||||
If NO image was grabbed:
|
||||
- **New icon with no image**: Ask the user to provide a screenshot. STOP.
|
||||
- **Follow-up with no image**: The existing `source.png` in the icon root carries forward. Copy it to `.ai/icon_{name}/{LETTER}/source.png`. If no source.png exists at all, ask the user for an image.
|
||||
|
||||
### Step 0g: Verify renderer
|
||||
|
||||
Locate the render tool (`codegen_style` with `--render-svg` mode):
|
||||
|
||||
```bash
|
||||
if [[ "$OSTYPE" == darwin* ]]; then
|
||||
ls out/Debug/codegen_style
|
||||
else
|
||||
ls out/Telegram/codegen/codegen/style/Debug/codegen_style.exe
|
||||
fi
|
||||
```
|
||||
|
||||
If missing, build it: `cmake --build out --config Debug --target codegen_style`
|
||||
|
||||
Test on a known good SVG (use the appropriate binary path for the OS):
|
||||
```bash
|
||||
CODEGEN=$(if [[ "$OSTYPE" == darwin* ]]; then echo out/Debug/codegen_style; else echo out/Telegram/codegen/codegen/style/Debug/codegen_style.exe; fi)
|
||||
$CODEGEN --render-svg Telegram/Resources/icons/menu/tag_add.svg .ai/icon_{name}/test_render.png 512
|
||||
```
|
||||
|
||||
If works → delete test render, set `RENDER_AVAILABLE = true`. If fails → `RENDER_AVAILABLE = false`.
|
||||
|
||||
## Phase 1: Vectorize & Post-process
|
||||
|
||||
### Step 1a: Call vectosolve
|
||||
|
||||
Use the `mcp__vectosolve__vectorize` tool with `file_path` set to the **absolute path** of `.ai/icon_{name}/{LETTER}/source.png`.
|
||||
|
||||
**If this fails, STOP IMMEDIATELY.** Do NOT try to generate the SVG manually or by any other means. Report the error to the user and let them fix the issue (bad API key, no credits, network error, etc.).
|
||||
|
||||
Save the returned SVG content to `.ai/icon_{name}/{LETTER}/raw_vectosolve.svg`.
|
||||
|
||||
The MCP tool calls the vectosolve API ($0.20/call). The API key is stored in `~/.claude.json` MCP config (never in the repository).
|
||||
|
||||
### Step 1b: Post-process the SVG
|
||||
|
||||
The vectosolve SVG will have colors from the mockup, arbitrary dimensions, and possibly a non-square aspect ratio from a non-square screenshot crop. Post-processing fixes this by adjusting the **viewBox** — leave path coordinates untouched.
|
||||
|
||||
**Do NOT transform path coordinates.** Vectosolve's paths are correct — the only thing wrong is the framing. All geometry adjustments are done by manipulating the `viewBox` and the `width`/`height` attributes.
|
||||
|
||||
#### Sub-step 1: Read the request and determine parameters
|
||||
|
||||
Before touching the SVG, determine these from the user's request and context.md:
|
||||
|
||||
1. **Output size** (`OUT_W × OUT_H`): default is `24px × 24px` for menu icons. The user may request different dimensions (e.g., 36×36, 48×48, or non-square). Always check the request.
|
||||
2. **Content padding**: default is ~2px equivalent on each side at the output scale (so content fills roughly (OUT_W-4) × (OUT_H-4)). The user may request different padding or edge-to-edge.
|
||||
3. **Centering**: default is centered both horizontally and vertically. The user may request specific alignment (e.g., "align to bottom").
|
||||
|
||||
#### Sub-step 2: Parse the raw SVG
|
||||
|
||||
1. Extract the `viewBox`: `viewBox="VB_X VB_Y VB_W VB_H"` (typically `0 0 W H`).
|
||||
2. Identify ALL paths. Classify each:
|
||||
- **Background**: a rect or path spanning the full viewBox (first path that's a simple rectangle matching the viewBox bounds). **Remove it entirely.**
|
||||
- **Content**: the actual icon shapes. **Keep these, paths unchanged.**
|
||||
3. If paths have `transform="translate(TX,TY)"` attributes, that's fine — keep them as-is. The viewBox framing will work regardless.
|
||||
|
||||
#### Sub-step 3: Compute the content bounding box
|
||||
|
||||
Estimate the bounding box of the content paths (after removing the background). You can either:
|
||||
- Eyeball it from the path coordinates (look at first/last M commands and extremes of curves)
|
||||
- Or for precision, write a quick script to parse the paths and find min/max X/Y
|
||||
|
||||
Call the result: `CX_MIN, CY_MIN, CX_MAX, CY_MAX`. Content dimensions: `CW = CX_MAX - CX_MIN`, `CH = CY_MAX - CY_MIN`.
|
||||
|
||||
#### Sub-step 4: Compute the new viewBox
|
||||
|
||||
The viewBox determines what part of the SVG coordinate space maps to the output rectangle. By expanding the viewBox beyond the content bounds, we add padding. By making the viewBox aspect ratio match the output aspect ratio, we prevent stretching.
|
||||
|
||||
1. **Output aspect ratio**: `OUT_AR = OUT_W / OUT_H` (for 24×24 this is 1.0).
|
||||
2. **Padding in SVG coordinates**: we want ~2px padding at output scale. The scale factor is `OUT_W / VB_CONTENT_W` approximately, so padding in SVG coords = `2 * (CW / (OUT_W - 4))` (or similar — the exact formula depends on which dimension is dominant). Simpler approach: aim for content to occupy ~83% of the viewBox (≈ 20/24), so:
|
||||
- `PADDED_W = CW / 0.83`
|
||||
- `PADDED_H = CH / 0.83`
|
||||
3. **Match output aspect ratio**: the viewBox aspect ratio must equal `OUT_AR` to avoid stretching.
|
||||
- If `PADDED_W / PADDED_H > OUT_AR`: width is dominant → `VB_W = PADDED_W`, `VB_H = VB_W / OUT_AR`
|
||||
- If `PADDED_W / PADDED_H < OUT_AR`: height is dominant → `VB_H = PADDED_H`, `VB_W = VB_H * OUT_AR`
|
||||
- If equal: `VB_W = PADDED_W`, `VB_H = PADDED_H`
|
||||
4. **Center the content** in the new viewBox:
|
||||
- `VB_X = CX_MIN - (VB_W - CW) / 2`
|
||||
- `VB_Y = CY_MIN - (VB_H - CH) / 2`
|
||||
- (Adjust if the user requested non-centered alignment)
|
||||
|
||||
The new viewBox is: `viewBox="VB_X VB_Y VB_W VB_H"`.
|
||||
|
||||
#### Sub-step 5: Recolor to white-on-transparent
|
||||
|
||||
- Replace ALL `fill` color values (anything that isn't `none`) with `#FFFFFF`.
|
||||
- Remove ALL `stroke` and `stroke-width` attributes entirely.
|
||||
- Remove `opacity` attributes if present.
|
||||
|
||||
#### Sub-step 6: Determine path composition
|
||||
|
||||
Look at the icon's visual structure and decide how paths should combine:
|
||||
- **Outlined shape** (e.g., circle outline with something inside): combine outer + inner cutout into one `<path>` with `fill-rule="evenodd"`.
|
||||
- **Separate distinct parts** (e.g., magnifying glass + checkmark): keep as separate `<path>` elements.
|
||||
- **Filled shape with cutout** (e.g., filled circle with checkmark punched out): combine into one path with `fill-rule="evenodd"`.
|
||||
|
||||
#### Sub-step 7: Assemble final SVG
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="{OUT_W}px" height="{OUT_H}px" viewBox="{VB_X} {VB_Y} {VB_W} {VB_H}" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="..." fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
```
|
||||
|
||||
- `width`/`height` = the output size from the request (default `24px`/`24px`).
|
||||
- `viewBox` = the computed viewBox from Sub-step 4. The SVG renderer maps this coordinate region to the output size.
|
||||
- Path `d` attributes are **unchanged** from vectosolve output (just background removed, colors replaced).
|
||||
- No `<title>`, `id`, `xmlns:xlink`, `version`, `class`, `style`, XML comments, `<metadata>`, or `preserveAspectRatio`.
|
||||
- No `<circle>`, `<rect>`, `<line>` — only `<path>`.
|
||||
|
||||
Write the final SVG to `.ai/icon_{name}/{LETTER}.svg`.
|
||||
|
||||
### Step 1c: Render
|
||||
|
||||
If `RENDER_AVAILABLE`:
|
||||
```bash
|
||||
$CODEGEN --render-svg ".ai/icon_{name}/{LETTER}.svg" ".ai/icon_{name}/render_{LETTER}.png" 512
|
||||
```
|
||||
|
||||
Read the render to visually verify the result.
|
||||
|
||||
## Phase 2: Review
|
||||
|
||||
After rendering, assess the result:
|
||||
|
||||
1. **Recognizable?** The icon should be clearly identifiable as the intended symbol.
|
||||
2. **Scale reasonable?** Should fill the space appropriately with ~2-3px padding.
|
||||
3. **Clean lines?** No broken paths, artifacts, or unwanted elements.
|
||||
4. **Correct colors?** All white on transparent (no leftover colors from the mockup).
|
||||
|
||||
If the result looks good → proceed to Phase 3 (Output).
|
||||
|
||||
If there are fixable issues (stray element, missed color, etc.) → fix the SVG directly, re-render, and re-check.
|
||||
|
||||
If the result is poor (vectosolve couldn't handle the input well) → report to the user and suggest:
|
||||
- Trying a cleaner/larger crop of the icon
|
||||
- Providing a different screenshot
|
||||
- Following up: `/icon {icon_name} <description of what to change>`
|
||||
|
||||
## Phase 3: Output
|
||||
|
||||
1. Read the `Target:` line from `.ai/icon_{name}/context.md` to get the output path.
|
||||
|
||||
2. Copy the final SVG to that target path (e.g., `Telegram/Resources/icons/menu/{icon_name}.svg`).
|
||||
|
||||
3. Update `.ai/icon_{name}/context.md` — append to the end:
|
||||
```
|
||||
## Latest Output
|
||||
Letter: {LETTER}
|
||||
Written to: {target_path}
|
||||
```
|
||||
|
||||
4. Report to the user:
|
||||
- Final icon file path
|
||||
- Number of vectosolve calls made (cost at $0.20/call)
|
||||
- Suggest verifying visually
|
||||
- Working directory `.ai/icon_{name}/` has all iterations
|
||||
- Elapsed time since `$START_TIME` (format `Xm Ys`)
|
||||
- Follow-up: `/icon {icon_name} <description of what to change>`
|
||||
|
||||
## Text-only Follow-ups (no new image)
|
||||
|
||||
When a follow-up has no attached image, the user wants to refine the existing SVG based on text feedback. In this case:
|
||||
|
||||
1. Skip Phase 1 (no vectosolve call needed).
|
||||
2. Read the latest SVG (`.ai/icon_{name}/{prev_letter}.svg`).
|
||||
3. Read the latest render if available.
|
||||
4. Apply the user's requested changes by editing the SVG directly.
|
||||
5. Save as `.ai/icon_{name}/{LETTER}.svg`.
|
||||
6. Render, review, and output as normal (Phases 1c → 3).
|
||||
|
||||
If the changes are too complex for manual SVG editing, suggest the user provide a new screenshot instead.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- If clipboard grab fails → tell user to re-copy and retry.
|
||||
- If vectosolve returns an error → report it and suggest a different/cleaner screenshot.
|
||||
- If vectosolve returns SVG that can't be parsed → save raw output for debugging, report to user.
|
||||
- If the render helper fails → set `RENDER_AVAILABLE = false`, continue with SVG-only review.
|
||||
- If post-processing produces a broken SVG → fall back to the raw vectosolve output and do lighter cleanup.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
description: Learn from corrections — examine staged vs unstaged diffs and optionally distill insights into AGENTS.md or REVIEW.md
|
||||
allowed-tools: Read, Edit, Bash(git diff:*), Bash(git status:*), Bash(git log:*), Bash(ls:*), AskUserQuestion
|
||||
---
|
||||
|
||||
# Reflect — Learn from Corrections
|
||||
|
||||
You are a reflection agent. Your job is to examine the difference between what an AI agent produced (staged changes) and what the user corrected (unstaged changes), and determine whether any **general, reusable insight** can be extracted and added to the project's coding guidelines.
|
||||
|
||||
**CRITICAL: Use extended thinking ultrathink for your analysis. This requires deep, careful reasoning.**
|
||||
|
||||
## Arguments
|
||||
|
||||
`$ARGUMENTS` = "$ARGUMENTS"
|
||||
|
||||
If `$ARGUMENTS` is provided, it is a task name (project name from the `/task` workflow). This means the agent was working within `.ai/<task-name>/` and you should read the task context for deeper understanding of what the agent was trying to do.
|
||||
|
||||
If `$ARGUMENTS` is empty, skip the task context step — just work from the diffs alone.
|
||||
|
||||
## Context
|
||||
|
||||
The workflow is:
|
||||
1. An AI agent implemented something and its changes were staged (`git add`).
|
||||
2. The user reviewed and corrected the agent's work. These corrections are unstaged.
|
||||
3. You are now invoked to reflect on what went wrong and whether it reveals a pattern.
|
||||
|
||||
## Step 1: Gather the Diffs and Task Context
|
||||
|
||||
Run these commands in parallel:
|
||||
|
||||
```bash
|
||||
git diff --cached # What the agent wrote (staged)
|
||||
git diff # What the user corrected (unstaged, on top of staged)
|
||||
git status # Which files are involved
|
||||
```
|
||||
|
||||
If either diff is empty, tell the user and stop. Both diffs must be non-empty for reflection to be meaningful.
|
||||
|
||||
### Task context (only if `$ARGUMENTS` is non-empty)
|
||||
|
||||
The task name is `$ARGUMENTS`. Read the task's project context:
|
||||
|
||||
1. Read `.ai/$ARGUMENTS/about.md` — the project-level description of what this feature does.
|
||||
2. Find the latest task iteration folder: list `.ai/$ARGUMENTS/` and pick the folder with the highest letter (`a`, `b`, `c`, ...).
|
||||
3. Read `.ai/$ARGUMENTS/<latest-letter>/context.md` — the detailed implementation context the agent was working from.
|
||||
|
||||
This helps you distinguish between:
|
||||
- **Task-specific mistakes** — the agent misunderstood this particular feature's requirements or made a wrong choice within the specific problem. These are NOT documentation-worthy.
|
||||
- **General convention mistakes** — the agent did something that violates a pattern the codebase follows broadly, regardless of which feature is being implemented. These ARE potentially documentation-worthy.
|
||||
|
||||
Having the task context makes this distinction much sharper. Without it, you might mistake a task-specific correction for a general pattern or vice versa.
|
||||
|
||||
## Step 2: Read the Current Guidelines
|
||||
|
||||
Read both files:
|
||||
- `AGENTS.md` — development guidelines: build system, coding style, API usage patterns, UI styling, localization, rpl, architectural conventions, "how to do things"
|
||||
- `REVIEW.md` — mechanical style and formatting rules: brace placement, operator position, type checks, variable initialization, call formatting
|
||||
|
||||
Read them carefully. You need to know exactly what's already documented to avoid duplicates and detect contradictions.
|
||||
|
||||
## Step 3: Analyze the Corrections
|
||||
|
||||
Now think deeply. For each correction the user made, ask yourself:
|
||||
|
||||
1. **What did the agent do wrong?** Understand the specific mistake.
|
||||
2. **Why was it wrong?** Identify the underlying principle.
|
||||
3. **Is this already covered by AGENTS.md or REVIEW.md?** Check carefully:
|
||||
- If the existing rule's scope, title, and examples **clearly cover** this exact scenario and the agent just ignored it — that's not a documentation problem. Skip it.
|
||||
- If the existing rule **technically applies** but its scope is too narrow, its examples don't illustrate this usage pattern, or its wording would reasonably lead an agent to think it doesn't apply here — **the rule needs improvement**. Treat this as a potential insight (broaden the scope, add examples, adjust wording). A rule that agents repeatedly violate is an ineffective rule.
|
||||
4. **Is this specific to this particular task, or is it general?** Most corrections are task-specific ("wrong variable here", "this should call that function instead"). These are NOT documentation-worthy. Only patterns that would apply across many different tasks are worth capturing.
|
||||
5. **Would documenting this actually help a future agent?** Some things are too context-dependent or too obvious to be useful as a written rule. Be honest about this.
|
||||
|
||||
## Step 4: Decision
|
||||
|
||||
After analysis, you MUST reach one of these conclusions:
|
||||
|
||||
### Conclusion A: No actionable insight
|
||||
|
||||
The corrections are purely task-specific, or the existing documentation clearly and specifically covers the exact scenario and the agent simply ignored it. Say what the corrections were and why no doc changes are needed. Then **stop**.
|
||||
|
||||
### Conclusion B: New insight found
|
||||
|
||||
You can articulate a **concise, general rule** that:
|
||||
- Applies broadly (not just to this one task)
|
||||
- Is not already documented
|
||||
- Would genuinely help a future agent avoid the same class of mistake
|
||||
- Can be expressed in a few sentences with a clear code example
|
||||
|
||||
If you have a new insight, proceed to Step 5.
|
||||
|
||||
### Conclusion C: Existing rule needs improvement
|
||||
|
||||
A rule already exists in AGENTS.md or REVIEW.md, but its **scope is too narrow**, its **examples don't cover** the pattern the agent encountered, or its **wording** would reasonably lead an agent to think the rule doesn't apply. The agent's mistake is evidence the rule isn't effective.
|
||||
|
||||
This is NOT the same as Conclusion A. The test: would a careful agent, reading the existing rule, clearly know it applies to this specific situation? If no — the rule needs to be broadened, its examples expanded, or its title/scope adjusted. Proceed to Step 5.
|
||||
|
||||
**Common signs of an ineffective rule:**
|
||||
- The rule's title or scope restricts it to a context narrower than the actual principle (e.g., "in localization calls" when the pattern applies generally)
|
||||
- The examples only show one usage pattern, and the agent encountered a different one
|
||||
- The wording describes *what* to use but not *when* — so agents only apply it in situations that look like the examples
|
||||
|
||||
## Step 5: Categorize and Check for Contradictions
|
||||
|
||||
### Where does it belong?
|
||||
|
||||
- **REVIEW.md** — if it's a mechanical/style rule: formatting, naming, syntax preferences, call structure, brace/operator placement, type usage patterns. Rules that can be checked by looking at code locally without understanding the broader feature.
|
||||
- **AGENTS.md** — if it's an architectural/behavioral guideline: how to use APIs, where to place code, design patterns, build conventions, module organization, reactive patterns (rpl), localization usage, style system usage. Rules that require understanding the broader context.
|
||||
|
||||
### Does it contradict existing content?
|
||||
|
||||
Read the target file again carefully. Check if:
|
||||
1. The new insight **contradicts** an existing rule — if so, do NOT just append or just remove. Instead, use AskUserQuestion to present both the existing rule and the new insight to the user, explain the contradiction, and ask how to reconcile them.
|
||||
2. The new insight **overlaps** with an existing rule — if so, consider whether the existing rule should be extended/refined rather than adding a separate entry.
|
||||
3. The new insight is **complementary** — it adds something new without conflicting. This is the simplest case.
|
||||
|
||||
## Step 6: Propose the Change
|
||||
|
||||
**Do NOT silently edit the files.** First, present your proposed change to the user:
|
||||
|
||||
- Quote the exact text you want to add or modify
|
||||
- Explain which file and where in the file
|
||||
- Explain why this is general enough to document
|
||||
- If modifying existing text, show the before and after
|
||||
|
||||
Use AskUserQuestion to get the user's approval before making any edit.
|
||||
|
||||
Only after the user approves, apply the edit using the Edit tool.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Keep docs lean and high-signal.** Don't add vague or overly specific rules. But don't default to inaction either — if the user had to manually fix something that a better-worded rule would have prevented, improving that rule is high-signal work.
|
||||
- **Never dump corrections verbatim.** The goal is distilled principles, not a changelog of mistakes.
|
||||
- **One insight per reflection, maximum.** If you think you see multiple insights, pick the strongest one. You can always run `/reflect` again next time.
|
||||
- **Keep the same style.** Match the formatting, tone, and level of detail of the target file. REVIEW.md uses specific before/after code examples. AGENTS.md uses explanatory sections with code snippets.
|
||||
- **Don't add "don't do X" rules.** Frame rules positively: "do Y" is better than "don't do X." Show the right way, not just the wrong way.
|
||||
- **No meta-commentary.** Don't add notes like "Added after reflection on..." — the rule should read as if it was always there.
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
description: Prepare changelog, set version, and commit a new release
|
||||
allowed-tools: Read, Bash, Edit, Grep, AskUserQuestion
|
||||
---
|
||||
|
||||
# Release — Changelog, Set Version, Commit
|
||||
|
||||
Full release flow: generate changelog entry, run `set_version`, and commit.
|
||||
|
||||
**Arguments:** `$ARGUMENTS` = "$ARGUMENTS"
|
||||
|
||||
Parse `$ARGUMENTS` for two optional parts (in any order):
|
||||
- A **version number** like `6.7` or `6.7.0` — if provided, use it as the new version exactly.
|
||||
- The word **"beta"** — if present, mark the release as beta.
|
||||
|
||||
If no version number is given, auto-increment the patch component (see step 2).
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Check git status is clean
|
||||
|
||||
Run `git status --porcelain`. If there are any uncommitted changes, **stop** and ask the user to commit or discard them before proceeding. Do not continue until status is clean.
|
||||
|
||||
### 2. Read the current changelog
|
||||
|
||||
Read `changelog.txt` from the repository root. Note the **latest version number** on the first line (e.g. `6.6.3 beta (12.03.26)`). Parse its major.minor.patch components.
|
||||
|
||||
### 3. Determine the new version number
|
||||
|
||||
- **If a version was provided in arguments**, use it directly (append `.0` if only major.minor was given).
|
||||
- **If no version was provided**, auto-increment from the latest changelog version:
|
||||
- If it was a beta, and the new release is **not** beta, reuse the same version number but drop "beta".
|
||||
- If the new release is beta and the latest was also beta with the same major.minor, bump patch.
|
||||
- Otherwise bump the patch component by 1.
|
||||
- Present the chosen version to the user and ask for confirmation before proceeding. If the user suggests a different version, use that instead.
|
||||
|
||||
### 4. Fetch tags and determine the last release tag
|
||||
|
||||
Run `git fetch origin --tags` first to ensure all tags from the public repository are available locally. Then run `git tag --sort=-v:refname` and find the most recent `v*` tag. This is the baseline for the diff.
|
||||
|
||||
### 5. Collect commits
|
||||
|
||||
Run `git log <last-tag>..HEAD --oneline` to get all commits since the last release.
|
||||
|
||||
### 6. Write the changelog entry
|
||||
|
||||
Analyze every commit message. Group them mentally into features, improvements, and bug fixes. Then produce **brief, user-facing bullet points** following these rules:
|
||||
|
||||
- **Style:** Match the existing changelog tone exactly — short, imperative sentences starting with a verb (Fix, Add, Allow, Show, Improve, Support…). Keep the trailing periods (the existing changelog uses them).
|
||||
- **Brevity:** Each bullet should be one short sentence, around 80 characters when possible. No implementation details. No commit hashes.
|
||||
- **Selection:** Only include changes that matter to end users. Skip CI, build infra, submodule bumps, code style, refactors, and intermediate WIP commits. Collapse many related commits (e.g. a dozen image-editor commits) into one or two bullets.
|
||||
- **Ordering:** Features first, then improvements, then bug fixes.
|
||||
- **Quantity:** Aim for 4-12 bullets total depending on the amount of changes.
|
||||
|
||||
### 7. Format and insert into changelog.txt
|
||||
|
||||
Use this exact format (date is today in DD.MM.YY):
|
||||
|
||||
```
|
||||
<version> [beta ](DD.MM.YY)
|
||||
|
||||
- Bullet one.
|
||||
- Bullet two.
|
||||
```
|
||||
|
||||
Prepend the new entry at the very top of `changelog.txt`, separated by a blank line from the previous first entry. Use the Edit tool.
|
||||
|
||||
### 8. Show the entry and wait for approval
|
||||
|
||||
Print the full changelog entry in chat and ask the user to review it. Tell them they can:
|
||||
- Approve as-is.
|
||||
- Edit `changelog.txt` directly in the IDE and tell you to continue.
|
||||
- Tell you what to change in chat.
|
||||
|
||||
**Do NOT proceed until the user explicitly approves.** If they request changes, apply them and show the updated entry again.
|
||||
|
||||
### 9. Run set_version
|
||||
|
||||
Once approved, run the `set_version` script from the repository root. On Windows:
|
||||
|
||||
```
|
||||
.\Telegram\build\set_version.bat <version_arg>
|
||||
```
|
||||
|
||||
Where `<version_arg>` is formatted as the `set_version` script expects:
|
||||
- Stable: `6.7.0` or `6.7`
|
||||
- Beta: `6.7.0.beta`
|
||||
|
||||
Verify the script exits successfully (exit code 0). If it fails, show the error and stop.
|
||||
|
||||
### 10. Commit
|
||||
|
||||
Stage all changes and create a commit. The commit message format:
|
||||
|
||||
**First line:**
|
||||
- For stable: `Version <major>.<minor>.` if patch is 0, otherwise `Version <major>.<minor>.<patch>.`
|
||||
- For beta: `Beta version <major>.<minor>.<patch>.`
|
||||
|
||||
**Then an empty line, then the changelog bullets.** Each bullet line (starting with `- `) must be wrapped at around 77-78 characters. When wrapping, break at logically correct places (between words/phrases) and indent continuation lines with two spaces.
|
||||
|
||||
Example commit message:
|
||||
```
|
||||
Beta version 6.6.3.
|
||||
|
||||
- Drawing tools in image editor
|
||||
(brush, marker, eraser, arrow).
|
||||
- Draw-to-reply button in media viewer.
|
||||
- Trim recorded voice messages.
|
||||
- Fix reorder freeze in chats list.
|
||||
```
|
||||
|
||||
Use a HEREDOC to pass the message to `git commit -a`.
|
||||
|
||||
### 11. Done
|
||||
|
||||
Run `git log -1` to show the resulting commit and confirm success.
|
||||
@@ -0,0 +1,11 @@
|
||||
param([string]$outPath)
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
||||
if ($img) {
|
||||
$img.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png)
|
||||
Write-Host "Saved to $outPath"
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "No image on clipboard"
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Grab clipboard image on macOS and save as PNG.
|
||||
outPath="$1"
|
||||
if [ -z "$outPath" ]; then
|
||||
echo "Usage: grab_clipboard.sh <output.png>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
osascript -e '
|
||||
set theFile to POSIX file "'"$outPath"'"
|
||||
try
|
||||
set theImage to the clipboard as «class PNGf»
|
||||
on error
|
||||
return "no image"
|
||||
end try
|
||||
set fh to open for access theFile with write permission
|
||||
write theImage to fh
|
||||
close access fh
|
||||
return "ok"
|
||||
' 2>/dev/null | grep -q "ok"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Saved to $outPath"
|
||||
exit 0
|
||||
else
|
||||
echo "No image on clipboard"
|
||||
exit 1
|
||||
fi
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
tool-cache: true
|
||||
|
||||
- name: Set up Docker Buildx.
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Libraries cache.
|
||||
uses: actions/cache@v5
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
restore-keys: ${{ runner.OS }}-libs-
|
||||
|
||||
- name: Libraries.
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: Telegram/build/docker/centos_env
|
||||
load: ${{ env.ONLY_CACHE == 'false' }}
|
||||
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
mkdir artifact
|
||||
mv Telegram.app artifact/
|
||||
mv Updater artifact/
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
cd $REPO_NAME/build
|
||||
mkdir artifact
|
||||
mv Telegram.app artifact/
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
with:
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
echo "SNAP_FILE=$SNAP_FILE" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.SNAP_FILE }}
|
||||
path: ${{ env.SNAP_FILE }}
|
||||
|
||||
@@ -54,6 +54,8 @@ jobs:
|
||||
exclude:
|
||||
- arch: arm64
|
||||
qt: ""
|
||||
- arch: x64_x86
|
||||
qt: qt6
|
||||
|
||||
env:
|
||||
UPLOAD_ARTIFACT: "true"
|
||||
@@ -226,7 +228,7 @@ jobs:
|
||||
mkdir artifact
|
||||
move %OUT%\Telegram.exe artifact/
|
||||
move %OUT%\Updater.exe artifact/
|
||||
- uses: actions/upload-artifact@v6
|
||||
- uses: actions/upload-artifact@v7
|
||||
name: Upload artifact.
|
||||
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
|
||||
with:
|
||||
|
||||
@@ -91,3 +91,6 @@
|
||||
[submodule "Telegram/ThirdParty/xdg-desktop-portal"]
|
||||
path = Telegram/ThirdParty/xdg-desktop-portal
|
||||
url = https://github.com/flatpak/xdg-desktop-portal.git
|
||||
[submodule "Telegram/lib_translate"]
|
||||
path = Telegram/lib_translate
|
||||
url = https://github.com/desktop-app/lib_translate
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Guide for Telegram Desktop
|
||||
# Agent Guide for Telegram Desktop
|
||||
|
||||
This guide defines repository-wide instructions for coding agents working with the Telegram Desktop codebase.
|
||||
|
||||
@@ -96,6 +96,21 @@ Retrying builds wastes time and context. The ONLY fix is for the user to close t
|
||||
1. **Always use Debug builds** - Release builds are extremely heavy
|
||||
2. **Don't build Release configuration** - it's too heavy for testing
|
||||
|
||||
## Text File Format
|
||||
|
||||
- On Windows, keep project text files with CRLF line endings.
|
||||
- Do not save source, header, build/config, style, or localization files as UTF-8 with BOM. Use UTF-8 without BOM.
|
||||
- When rewriting project text files for normalization, preserve file content otherwise and do not introduce a BOM.
|
||||
|
||||
## Local Storage Serialization
|
||||
|
||||
Both app-level (`Core::Settings`) and session-level (`Main::SessionSettings`) use sequential binary serialization via `QDataStream`. Key rules:
|
||||
|
||||
- New fields must ALWAYS be appended at the **end** of the stream, never inserted in the middle
|
||||
- Reading new fields must be guarded with `!stream.atEnd()` and provide a meaningful default/fallback
|
||||
- Inserting in the middle breaks reading of data saved by older versions (the new read code consumes bytes that belong to subsequent fields)
|
||||
- For simple flags and values, prefer using the generic KV prefs facility (`writePref<Type>` / `readPref<Type>`) instead of adding to the binary stream -- this avoids serialization ordering issues entirely
|
||||
|
||||
---
|
||||
|
||||
# Development Guidelines
|
||||
|
||||
@@ -114,7 +114,7 @@ bool _expanded = false;
|
||||
SomeType *_pointer = nullptr;
|
||||
```
|
||||
|
||||
## Prefer tr:: projections over Ui::Text:: in localization calls
|
||||
## Use tr:: projections for TextWithEntities
|
||||
|
||||
Inside `tr::lng_...()` calls, always use the `tr::` projection helpers instead of their `Ui::Text::` equivalents. The `tr::` helpers are shorter and work uniformly as both placeholder wrappers and final projectors.
|
||||
|
||||
@@ -145,6 +145,19 @@ tr::lng_some_key(
|
||||
tr::rich)
|
||||
```
|
||||
|
||||
Also use `tr::marked()` as the standard way to create `TextWithEntities` — not just as a projector:
|
||||
|
||||
```cpp
|
||||
// BAD - verbose constructor:
|
||||
auto text = TextWithEntities();
|
||||
auto text = TextWithEntities{ u"hello"_q };
|
||||
auto text = TextWithEntities().append(u"hello"_q);
|
||||
|
||||
// GOOD - concise:
|
||||
auto text = tr::marked();
|
||||
auto text = tr::marked(u"hello"_q);
|
||||
```
|
||||
|
||||
## Multi-line calls — one argument per line
|
||||
|
||||
When a function call doesn't fit on one line, put each argument on its own line. Don't group "logical pairs" on the same line — it creates inconsistent line lengths and makes diffs noisier.
|
||||
@@ -173,4 +186,329 @@ auto text = tr::lng_settings_title(tr::now);
|
||||
## std::optional access — avoid value()
|
||||
|
||||
Do not call `std::optional::value()` because it throws an exception that is not available on older macOS targets. Use `has_value()`, `value_or()`, `operator bool()`, or `operator*` instead.
|
||||
|
||||
## Sort includes alphabetically, nested folders first
|
||||
|
||||
After the file's own header, sort `#include` directives alphabetically with two special rules:
|
||||
|
||||
1. **Nested folders before files** in the same directory — like Finder / File Explorer (folders first, then files). E.g. `ui/controls/button.h` sorts before `ui/abstract_button.h`.
|
||||
2. **Style includes (`styles/style_*.h`) always go last**, separated from the rest.
|
||||
|
||||
```cpp
|
||||
// BAD - arbitrary order, style mixed in:
|
||||
#include "media/audio/media_audio.h"
|
||||
#include "styles/style_media_player.h"
|
||||
#include "data/data_document.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
// GOOD - alphabetical, folders first, styles last:
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_document.h"
|
||||
#include "media/audio/media_audio.h"
|
||||
|
||||
#include "styles/style_media_player.h"
|
||||
```
|
||||
|
||||
## Use C++17 nested namespace syntax
|
||||
|
||||
Use `namespace A::B {` instead of nesting `namespace A { namespace B {`. The closing comment mirrors the opening: `} // namespace A::B`.
|
||||
|
||||
```cpp
|
||||
// BAD - old-style nesting:
|
||||
namespace Media {
|
||||
namespace Player {
|
||||
...
|
||||
} // namespace Player
|
||||
} // namespace Media
|
||||
|
||||
// GOOD - C++17 nested:
|
||||
namespace Media::Player {
|
||||
...
|
||||
} // namespace Media::Player
|
||||
```
|
||||
|
||||
## Merge consecutive branches with identical bodies
|
||||
|
||||
When two or more consecutive `if` / `else if` branches execute the same code, combine their conditions into a single branch.
|
||||
|
||||
```cpp
|
||||
// BAD - duplicated body:
|
||||
if (!document) {
|
||||
finalize();
|
||||
return;
|
||||
}
|
||||
if (!document->isSong()) {
|
||||
finalize();
|
||||
return;
|
||||
}
|
||||
|
||||
// GOOD - combined:
|
||||
if (!document || !document->isSong()) {
|
||||
finalize();
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
## Use base::take for read-and-reset
|
||||
|
||||
When you need to read a variable's current value and reset it in one step, use `base::take(var)` instead of manually copying and clearing. `base::take` returns the old value and resets the variable to its default-constructed state.
|
||||
|
||||
```cpp
|
||||
// BAD - manual read + reset:
|
||||
if (_playing) {
|
||||
_listenedMs += crl::now() - _playStartedAt;
|
||||
_playing = false;
|
||||
}
|
||||
|
||||
// GOOD:
|
||||
if (base::take(_playing)) {
|
||||
_listenedMs += crl::now() - _playStartedAt;
|
||||
}
|
||||
|
||||
// BAD - copy fields then clear them one by one:
|
||||
const auto document = _document;
|
||||
const auto contextId = _contextId;
|
||||
_document = nullptr;
|
||||
_listenedMs = 0;
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
// GOOD - take everything upfront, then validate:
|
||||
const auto document = base::take(_document);
|
||||
const auto contextId = base::take(_contextId);
|
||||
const auto duration = static_cast<int>(base::take(_listenedMs) / 1000);
|
||||
if (!document || duration <= 0) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
## Don't wrap tr:: lang keys in rpl::single
|
||||
|
||||
`tr::lng_*()` (without `tr::now`) already returns an `rpl::producer`. Wrapping a snapshot in `rpl::single()` defeats live language switching — the value is captured once and never updates. Just call the lang key without `tr::now`.
|
||||
|
||||
```cpp
|
||||
// BAD - frozen snapshot, won't update on language change:
|
||||
rpl::single(tr::lng_ai_compose_title(tr::now))
|
||||
|
||||
// GOOD - live producer that updates automatically:
|
||||
tr::lng_ai_compose_title()
|
||||
```
|
||||
|
||||
## Extract method definitions from local classes
|
||||
|
||||
When defining local classes (e.g. in anonymous namespaces), keep the class body compact — only declarations. Put all method definitions **after** all class definitions. This avoids unnecessary nesting inside the class body and keeps methods at the same indentation level as free functions.
|
||||
|
||||
```cpp
|
||||
// BAD - methods defined inline, adding a nesting level:
|
||||
class MyWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
MyWidget(QWidget *parent)
|
||||
: RpWidget(parent) {
|
||||
// ... 20 lines of setup
|
||||
}
|
||||
|
||||
void setActive(bool active) {
|
||||
_active = active;
|
||||
update();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override {
|
||||
// ... 30 lines of painting
|
||||
}
|
||||
|
||||
private:
|
||||
bool _active = false;
|
||||
|
||||
};
|
||||
|
||||
// GOOD - class is a compact declaration, methods defined after:
|
||||
class MyWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
MyWidget(QWidget *parent, QString label);
|
||||
|
||||
void setActive(bool active);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
bool _active = false;
|
||||
|
||||
};
|
||||
|
||||
MyWidget::MyWidget(QWidget *parent, QString label)
|
||||
: RpWidget(parent) {
|
||||
// ... 20 lines of setup
|
||||
}
|
||||
|
||||
void MyWidget::setActive(bool active) {
|
||||
_active = active;
|
||||
update();
|
||||
}
|
||||
|
||||
void MyWidget::paintEvent(QPaintEvent *e) {
|
||||
// ... 30 lines of painting
|
||||
}
|
||||
```
|
||||
|
||||
When there are multiple local classes, put **all class definitions first**, then **all method definitions** after. This keeps the declarations readable as an overview.
|
||||
|
||||
## Use RAII for resource cleanup
|
||||
|
||||
When working with raw resources (Win32 HANDLEs, file descriptors, COM objects), use `gsl::finally` or a dedicated RAII wrapper for cleanup instead of calling release functions manually. Manual cleanup breaks when early returns are added later.
|
||||
|
||||
```cpp
|
||||
// BAD - manual cleanup, fragile with early returns:
|
||||
const auto snapshot = CreateToolhelp32Snapshot(...);
|
||||
if (snapshot != INVALID_HANDLE_VALUE) {
|
||||
// ... logic that might grow early returns ...
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
// GOOD - RAII guard, cleanup runs on any exit path:
|
||||
const auto snapshot = CreateToolhelp32Snapshot(...);
|
||||
if (snapshot == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
const auto guard = gsl::finally([&] {
|
||||
CloseHandle(snapshot);
|
||||
});
|
||||
// ... logic, early returns are safe ...
|
||||
```
|
||||
|
||||
## Extract substantial logic from lambdas
|
||||
|
||||
When a lambda grows beyond a few lines of self-contained logic, extract it into a named function (free function in anonymous namespace, or a private method). Lambdas should primarily be glue — captures, dispatch, short transforms. This applies when the lambda's captures are minimal and can easily become function parameters. When a lambda captures many variables from its surrounding context, it may be cleaner to keep it inline.
|
||||
|
||||
```cpp
|
||||
// BAD - substantial logic buried in a lambda:
|
||||
crl::async([=] {
|
||||
auto found = false;
|
||||
auto pe = PROCESSENTRY32();
|
||||
pe.dwSize = sizeof(PROCESSENTRY32);
|
||||
const auto snapshot = CreateToolhelp32Snapshot(...);
|
||||
if (snapshot != INVALID_HANDLE_VALUE) {
|
||||
for (...) {
|
||||
if (/* match */) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
crl::on_main(weak, [=] { handle(found); });
|
||||
});
|
||||
|
||||
// GOOD - logic extracted, lambda is just glue:
|
||||
crl::async([=] {
|
||||
const auto found = FindRunningReader();
|
||||
crl::on_main(weak, [=] { handle(found); });
|
||||
});
|
||||
```
|
||||
|
||||
## Data-driven matching over chained conditions
|
||||
|
||||
When comparing a value against multiple known constants, store them in a collection and loop instead of chaining `||` conditions. Easier to extend, less repetition, and reads as data rather than logic.
|
||||
|
||||
```cpp
|
||||
// BAD - repetitive chain, hard to extend:
|
||||
if (_wcsicmp(name, L"Narrator.exe") == 0
|
||||
|| _wcsicmp(name, L"nvda.exe") == 0
|
||||
|| _wcsicmp(name, L"jfw.exe") == 0
|
||||
|| _wcsicmp(name, L"Zt.exe") == 0) {
|
||||
|
||||
// GOOD - data-driven, easy to extend:
|
||||
const auto list = std::array{
|
||||
L"Narrator.exe",
|
||||
L"nvda.exe",
|
||||
L"jfw.exe",
|
||||
L"Zt.exe",
|
||||
};
|
||||
for (const auto &entry : list) {
|
||||
if (_wcsicmp(name, entry) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Use !isHidden() for logic checks, not isVisible()
|
||||
|
||||
When you call `show()` / `hide()` / `setVisible()` on a widget and later branch on that state, always check `!isHidden()` (the widget's own flag) — never `isVisible()`. `isVisible()` returns `true` only when the widget **and every ancestor** are visible, so it silently returns `false` during parent show-animations, before the parent is laid out, etc. `isHidden()` reflects exactly the flag you set.
|
||||
|
||||
```cpp
|
||||
// BAD — breaks when parent is still animating / not yet shown:
|
||||
child->setVisible(true);
|
||||
// ... later, in resizeGetHeight or similar:
|
||||
if (child->isVisible()) { // false if parent isn't visible yet!
|
||||
child->moveToRight(x, y, w);
|
||||
}
|
||||
|
||||
// GOOD — checks the widget's own state:
|
||||
if (!child->isHidden()) {
|
||||
child->moveToRight(x, y, w);
|
||||
}
|
||||
```
|
||||
|
||||
The same applies to any logic that depends on a previous `show()`/`hide()` call: skip blocks, layout branches, opacity decisions, etc.
|
||||
|
||||
## Consolidate make_state calls into a single State struct
|
||||
|
||||
Every `make_state` is a separate heap allocation. When a function needs multiple pieces of lambda-captured mutable state, define a local `struct State` with all fields and call `make_state<State>()` once, then capture the resulting pointer everywhere.
|
||||
|
||||
```cpp
|
||||
// BAD - two allocations:
|
||||
const auto shown = lifetime.make_state<bool>(false);
|
||||
const auto count = lifetime.make_state<int>(0);
|
||||
|
||||
// GOOD - one allocation:
|
||||
struct State {
|
||||
bool shown = false;
|
||||
int count = 0;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
```
|
||||
|
||||
## Use trailing return type when the return type doesn't fit on one line
|
||||
|
||||
When a function's return type is long enough that the declaration would need a line break between the return type and the function name, use trailing return type syntax (`auto ... -> Type`) to keep the function name on the opening line.
|
||||
|
||||
```cpp
|
||||
// BAD - return type orphaned on its own line:
|
||||
not_null<HistoryView::Controls::ComposeAiButton*>
|
||||
SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args);
|
||||
|
||||
// GOOD - trailing return type keeps name visible:
|
||||
auto SetupCaptionAiButton(SetupCaptionAiButtonArgs &&args)
|
||||
-> not_null<HistoryView::Controls::ComposeAiButton*>;
|
||||
```
|
||||
|
||||
## Mind data structure sizes and alignment
|
||||
|
||||
When adding fields to a class or struct, consider the memory layout. A standalone `bool` between two pointer-sized fields wastes 7 bytes to alignment padding. Review new fields for packing opportunities:
|
||||
|
||||
- If the struct already has bitfields, pack new boolean flags as `: 1` members rather than standalone `bool`.
|
||||
- If alignment leaves a gap (e.g., an `int` followed by a pointer), consider whether a new small field can fill it.
|
||||
- For classes instantiated in large quantities (per-message, per-element, per-row), every wasted byte is multiplied thousands of times.
|
||||
|
||||
```cpp
|
||||
// BAD - standalone bool adds 8 bytes (1 + 7 padding) before the next pointer:
|
||||
mutable bool _myFlag = false;
|
||||
mutable std::unique_ptr<Foo> _foo;
|
||||
|
||||
// GOOD - packed into existing bitfield group, no extra bytes:
|
||||
mutable uint32 _myFlag : 1 = 0;
|
||||
```
|
||||
|
||||
## Static member functions use PascalCase
|
||||
|
||||
Non-static member functions use camelCase (`startBatch`, `finalize`). Static member functions use PascalCase (`ShouldTrack`, `Parse`, `Create`), matching the convention for free functions.
|
||||
|
||||
```cpp
|
||||
// BAD - camelCase for static method:
|
||||
[[nodiscard]] static bool shouldTrack(not_null<HistoryItem*> item);
|
||||
|
||||
// GOOD - PascalCase for static method:
|
||||
[[nodiscard]] static bool ShouldTrack(not_null<HistoryItem*> item);
|
||||
```
|
||||
|
||||
@@ -16,6 +16,7 @@ add_subdirectory(lib_spellcheck)
|
||||
add_subdirectory(lib_storage)
|
||||
add_subdirectory(lib_lottie)
|
||||
add_subdirectory(lib_qr)
|
||||
add_subdirectory(lib_translate)
|
||||
add_subdirectory(lib_webrtc)
|
||||
add_subdirectory(lib_webview)
|
||||
add_subdirectory(codegen)
|
||||
@@ -35,6 +36,7 @@ include(cmake/td_mtproto.cmake)
|
||||
include(cmake/td_scheme.cmake)
|
||||
include(cmake/td_tde2e.cmake)
|
||||
include(cmake/td_ui.cmake)
|
||||
include(cmake/telegram_apple_swift_runtime.cmake)
|
||||
include(cmake/generate_appstream_changelog.cmake)
|
||||
include(cmake/td_forkgram.cmake)
|
||||
|
||||
@@ -80,6 +82,7 @@ PRIVATE
|
||||
desktop-app::lib_storage
|
||||
desktop-app::lib_lottie
|
||||
desktop-app::lib_qr
|
||||
desktop-app::lib_translate
|
||||
desktop-app::lib_webview
|
||||
desktop-app::lib_ffmpeg
|
||||
desktop-app::lib_stripe
|
||||
@@ -96,6 +99,8 @@ PRIVATE
|
||||
desktop-app::external_xxhash
|
||||
)
|
||||
|
||||
telegram_add_apple_swift_runtime(Telegram)
|
||||
|
||||
target_precompile_headers(Telegram PRIVATE $<$<COMPILE_LANGUAGE:CXX,OBJCXX>:${src_loc}/stdafx.h>)
|
||||
nice_target_sources(Telegram ${src_loc}
|
||||
PRIVATE
|
||||
@@ -129,6 +134,8 @@ PRIVATE
|
||||
api/api_common.h
|
||||
api/api_confirm_phone.cpp
|
||||
api/api_confirm_phone.h
|
||||
api/api_compose_with_ai.cpp
|
||||
api/api_compose_with_ai.h
|
||||
api/api_credits.cpp
|
||||
api/api_credits.h
|
||||
api/api_credits_history_entry.cpp
|
||||
@@ -162,6 +169,10 @@ PRIVATE
|
||||
api/api_premium.h
|
||||
api/api_premium_option.cpp
|
||||
api/api_premium_option.h
|
||||
api/api_reactions_notify_settings.cpp
|
||||
api/api_reactions_notify_settings.h
|
||||
api/api_read_metrics.cpp
|
||||
api/api_read_metrics.h
|
||||
api/api_report.cpp
|
||||
api/api_report.h
|
||||
api/api_ringtones.cpp
|
||||
@@ -222,6 +233,8 @@ PRIVATE
|
||||
boxes/peers/channel_ownership_transfer.h
|
||||
boxes/peers/choose_peer_box.cpp
|
||||
boxes/peers/choose_peer_box.h
|
||||
boxes/peers/create_managed_bot_box.cpp
|
||||
boxes/peers/create_managed_bot_box.h
|
||||
boxes/peers/edit_contact_box.cpp
|
||||
boxes/peers/edit_contact_box.h
|
||||
boxes/peers/edit_forum_topic_box.cpp
|
||||
@@ -367,6 +380,8 @@ PRIVATE
|
||||
boxes/stickers_box.h
|
||||
boxes/transfer_gift_box.cpp
|
||||
boxes/transfer_gift_box.h
|
||||
boxes/compose_ai_box.cpp
|
||||
boxes/compose_ai_box.h
|
||||
boxes/translate_box.cpp
|
||||
boxes/translate_box.h
|
||||
boxes/url_auth_box.cpp
|
||||
@@ -492,6 +507,7 @@ PRIVATE
|
||||
chat_helpers/ttl_media_layer_widget.h
|
||||
core/application.cpp
|
||||
core/application.h
|
||||
core/cached_webview_availability.h
|
||||
core/bank_card_click_handler.cpp
|
||||
core/bank_card_click_handler.h
|
||||
core/base_integration.cpp
|
||||
@@ -682,6 +698,8 @@ PRIVATE
|
||||
data/data_photo_media.h
|
||||
data/data_poll.cpp
|
||||
data/data_poll.h
|
||||
data/data_poll_messages.cpp
|
||||
data/data_poll_messages.h
|
||||
data/data_premium_limits.cpp
|
||||
data/data_premium_limits.h
|
||||
data/data_pts_waiter.cpp
|
||||
@@ -816,6 +834,8 @@ PRIVATE
|
||||
history/admin_log/history_admin_log_section.cpp
|
||||
history/admin_log/history_admin_log_section.h
|
||||
history/view/controls/compose_controls_common.h
|
||||
history/view/controls/history_view_compose_ai_button.cpp
|
||||
history/view/controls/history_view_compose_ai_button.h
|
||||
history/view/controls/history_view_compose_controls.cpp
|
||||
history/view/controls/history_view_compose_controls.h
|
||||
history/view/controls/history_view_compose_media_edit_manager.cpp
|
||||
@@ -878,6 +898,8 @@ PRIVATE
|
||||
history/view/media/history_view_photo.h
|
||||
history/view/media/history_view_poll.cpp
|
||||
history/view/media/history_view_poll.h
|
||||
history/view/media/menu/history_view_poll_menu.cpp
|
||||
history/view/media/menu/history_view_poll_menu.h
|
||||
history/view/media/history_view_premium_gift.cpp
|
||||
history/view/media/history_view_premium_gift.h
|
||||
history/view/media/history_view_save_document_action.cpp
|
||||
@@ -939,6 +961,12 @@ PRIVATE
|
||||
history/view/history_view_corner_buttons.h
|
||||
history/view/history_view_cursor_state.cpp
|
||||
history/view/history_view_cursor_state.h
|
||||
history/view/history_view_draw_to_reply.cpp
|
||||
history/view/history_view_draw_to_reply.h
|
||||
history/view/history_view_add_poll_option.cpp
|
||||
history/view/history_view_add_poll_option.h
|
||||
history/view/history_view_element_overlay.cpp
|
||||
history/view/history_view_element_overlay.h
|
||||
history/view/history_view_element.cpp
|
||||
history/view/history_view_element.h
|
||||
history/view/history_view_emoji_interactions.cpp
|
||||
@@ -969,6 +997,8 @@ PRIVATE
|
||||
history/view/history_view_quick_action.h
|
||||
history/view/history_view_reaction_preview.cpp
|
||||
history/view/history_view_reaction_preview.h
|
||||
history/view/history_view_read_metrics_tracker.cpp
|
||||
history/view/history_view_read_metrics_tracker.h
|
||||
history/view/history_view_reply.cpp
|
||||
history/view/history_view_reply.h
|
||||
history/view/history_view_reply_button.cpp
|
||||
@@ -1101,6 +1131,8 @@ PRIVATE
|
||||
info/peer_gifts/info_peer_gifts_common.h
|
||||
info/peer_gifts/info_peer_gifts_widget.cpp
|
||||
info/peer_gifts/info_peer_gifts_widget.h
|
||||
info/polls/info_polls_list_widget.cpp
|
||||
info/polls/info_polls_list_widget.h
|
||||
info/polls/info_polls_results_inner_widget.cpp
|
||||
info/polls/info_polls_results_inner_widget.h
|
||||
info/polls/info_polls_results_widget.cpp
|
||||
@@ -1242,6 +1274,12 @@ PRIVATE
|
||||
lang/lang_numbers_animation.h
|
||||
lang/lang_translator.cpp
|
||||
lang/lang_translator.h
|
||||
lang/translate_mtproto_provider.cpp
|
||||
lang/translate_mtproto_provider.h
|
||||
lang/translate_provider.cpp
|
||||
lang/translate_provider.h
|
||||
lang/translate_url_provider.cpp
|
||||
lang/translate_url_provider.h
|
||||
layout/layout_document_generic_preview.cpp
|
||||
layout/layout_document_generic_preview.h
|
||||
layout/layout_item_base.cpp
|
||||
@@ -1264,6 +1302,8 @@ PRIVATE
|
||||
main/session/session_show.h
|
||||
media/audio/media_audio.cpp
|
||||
media/audio/media_audio.h
|
||||
media/audio/media_audio_edit.cpp
|
||||
media/audio/media_audio_edit.h
|
||||
media/audio/media_audio_capture.cpp
|
||||
media/audio/media_audio_capture.h
|
||||
media/audio/media_audio_capture_common.h
|
||||
@@ -1283,6 +1323,8 @@ PRIVATE
|
||||
media/player/media_player_float.h
|
||||
media/player/media_player_instance.cpp
|
||||
media/player/media_player_instance.h
|
||||
media/player/media_player_listen_tracker.cpp
|
||||
media/player/media_player_listen_tracker.h
|
||||
media/player/media_player_panel.cpp
|
||||
media/player/media_player_panel.h
|
||||
media/player/media_player_volume_controller.cpp
|
||||
@@ -1375,6 +1417,8 @@ PRIVATE
|
||||
media/system_media_controls_manager.cpp
|
||||
menu/menu_antispam_validator.cpp
|
||||
menu/menu_antispam_validator.h
|
||||
menu/menu_dock.cpp
|
||||
menu/menu_dock.h
|
||||
menu/menu_item_download_files.cpp
|
||||
menu/menu_item_download_files.h
|
||||
menu/menu_item_rate_transcribe_session.cpp
|
||||
@@ -1417,6 +1461,8 @@ PRIVATE
|
||||
overview/overview_layout.cpp
|
||||
overview/overview_layout.h
|
||||
overview/overview_layout_delegate.h
|
||||
poll/poll_media_upload.cpp
|
||||
poll/poll_media_upload.h
|
||||
passport/passport_encryption.cpp
|
||||
passport/passport_encryption.h
|
||||
passport/passport_form_controller.cpp
|
||||
@@ -1458,6 +1504,8 @@ PRIVATE
|
||||
platform/linux/overlay_widget_linux.h
|
||||
platform/linux/specific_linux.cpp
|
||||
platform/linux/specific_linux.h
|
||||
platform/linux/translate_provider_linux.cpp
|
||||
platform/linux/translate_provider_linux.h
|
||||
platform/linux/tray_linux.cpp
|
||||
platform/linux/tray_linux.h
|
||||
platform/linux/webauthn_linux.cpp
|
||||
@@ -1478,8 +1526,10 @@ PRIVATE
|
||||
platform/mac/specific_mac.h
|
||||
platform/mac/specific_mac_p.mm
|
||||
platform/mac/specific_mac_p.h
|
||||
platform/mac/tray_mac.mm
|
||||
platform/mac/translate_provider_mac.h
|
||||
platform/mac/translate_provider_mac.mm
|
||||
platform/mac/tray_mac.h
|
||||
platform/mac/tray_mac.mm
|
||||
platform/mac/webauthn_mac.mm
|
||||
platform/mac/window_title_mac.mm
|
||||
platform/mac/touchbar/items/mac_formatter_item.h
|
||||
@@ -1513,6 +1563,7 @@ PRIVATE
|
||||
platform/win/overlay_widget_win.h
|
||||
platform/win/specific_win.cpp
|
||||
platform/win/specific_win.h
|
||||
platform/win/translate_provider_win.h
|
||||
platform/win/tray_win.cpp
|
||||
platform/win/tray_win.h
|
||||
platform/win/webauthn_win.cpp
|
||||
@@ -1533,6 +1584,7 @@ PRIVATE
|
||||
platform/platform_overlay_widget.cpp
|
||||
platform/platform_overlay_widget.h
|
||||
platform/platform_specific.h
|
||||
platform/platform_translate_provider.h
|
||||
platform/platform_tray.h
|
||||
platform/platform_webauthn.h
|
||||
platform/platform_window_title.h
|
||||
@@ -1598,6 +1650,8 @@ PRIVATE
|
||||
settings/settings_codes.h
|
||||
settings/settings_common_session.cpp
|
||||
settings/settings_common_session.h
|
||||
settings/detailed_settings_button.cpp
|
||||
settings/detailed_settings_button.h
|
||||
settings/sections/settings_credits.cpp
|
||||
settings/sections/settings_credits.h
|
||||
settings/settings_credits_graphics.cpp
|
||||
@@ -1628,6 +1682,8 @@ PRIVATE
|
||||
settings/sections/settings_notifications.h
|
||||
settings/sections/settings_privacy_security.cpp
|
||||
settings/sections/settings_privacy_security.h
|
||||
settings/sections/settings_notifications_reactions.cpp
|
||||
settings/sections/settings_notifications_reactions.h
|
||||
settings/sections/settings_notifications_type.cpp
|
||||
settings/sections/settings_notifications_type.h
|
||||
settings/sections/settings_passkeys.cpp
|
||||
@@ -1715,6 +1771,8 @@ PRIVATE
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/chat/sponsored_message_bar.cpp
|
||||
ui/chat/sponsored_message_bar.h
|
||||
ui/controls/compose_ai_button_factory.cpp
|
||||
ui/controls/compose_ai_button_factory.h
|
||||
ui/controls/emoji_button_factory.cpp
|
||||
ui/controls/emoji_button_factory.h
|
||||
ui/controls/location_picker.cpp
|
||||
@@ -1744,8 +1802,6 @@ PRIVATE
|
||||
ui/image/image_location_factory.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/toast/toast_lottie_icon.cpp
|
||||
ui/toast/toast_lottie_icon.h
|
||||
ui/widgets/expandable_peer_list.cpp
|
||||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/chat_filters_tabs_strip.cpp
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="96.5 111.3 766.5 766.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M 498.706 365.42 C 515.594 363.348 538.563 365.861 548.485 381.651 C 566.807 410.809 576.751 446.661 588.311 479.176 L 637.399 618.626 L 665.78 698.579 C 676.659 729.146 687.226 748.313 682.54 781.481 C 675.846 788.102 668.071 795.055 658.868 797.629 C 651.616 799.698 643.834 798.72 637.319 794.919 C 631.617 791.632 622.75 782.719 620.419 776.738 C 608.192 745.354 594.733 707.965 586.262 675.669 C 569.761 672.672 533.607 673.757 515.5 673.803 L 465.584 673.663 C 429.007 673.61 428.662 668.91 417.869 707.395 C 410.414 728.146 405.333 752.038 396.071 771.94 C 379.585 807.367 355.479 802.277 330.202 783.696 C 330.019 782.164 329.86 780.629 329.726 779.091 C 328.901 769.751 329.769 760.338 332.289 751.306 C 338.538 728.396 348.963 702.053 357.021 679.401 L 394.065 573.806 L 430.927 469.343 C 440.963 440.571 450.771 403.685 469.355 379.052 C 476.858 369.108 486.798 366.389 498.706 365.42 z M 504.968 436.525 C 508.866 440.798 524.908 486.278 526.975 494.054 C 537.563 533.876 560.589 582.995 564.841 623.288 C 546.777 622.864 528.711 622.571 510.642 622.408 C 489.848 622.27 469.054 622.56 448.271 623.279 C 449.463 605.6 468.851 551.806 475.731 531.511 C 486.173 500.704 494.943 466.856 504.968 436.525 z" fill="#FFFFFF"></path>
|
||||
<path d="M 754.216 524.167 C 763.771 523.439 778.578 525.381 783.67 533.512 C 790.57 544.531 788.595 576.397 788.599 589.29 L 788.562 654.629 C 788.553 691.578 790.329 746.525 787.335 782.106 C 781.652 787.948 775.21 792.333 768.254 796.474 C 752.507 799.502 746.874 792.906 735.396 783.22 C 733.424 762.938 733.05 546.574 736.372 537.446 C 738.677 531.114 748.456 526.925 754.216 524.167 z" fill="#FFFFFF"></path>
|
||||
<path d="M 757.56 426.311 C 768.63 427.03 775.49 429.481 785.226 434.328 C 797.887 454.333 796.6 461.896 785.005 482.127 C 777.16 485.341 772.393 487.022 764.241 489.32 C 731.637 497.151 705.869 438.865 757.56 426.311 z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.1 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="96.5 111.3 766.5 766.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M 361.507 181.705 C 372.454 181.635 380.002 234.986 387.127 242.295 C 407.219 262.908 437.857 263.914 463.429 280.408 C 440.851 302.031 406.127 295.636 389.728 315.608 C 381.989 325.033 369.855 367.603 363.046 382.355 C 351.465 373.05 343.119 333.179 332.642 316.843 C 318.951 295.494 264.524 299.57 262.849 280.314 C 270.405 265.304 317.273 260.399 329.146 248.809 C 346.343 232.022 342.909 197.93 361.507 181.705 z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 668 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="96.5 111.3 766.5 766.5" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M 227.159 367.788 C 233.561 369.307 240.228 369.662 246.771 369.89 C 248.488 420.574 264.795 427.64 311.005 436.455 C 310.491 445.011 310.17 446.339 311.45 454.821 C 305.224 455.372 297.895 455.922 291.885 456.866 C 243.817 464.419 258.4 489.944 240.267 514.706 C 237.443 517.55 234.726 520.712 231.065 522.27 C 229.488 521.415 227.953 519.681 227.797 517.859 C 223.678 469.953 209.284 457.548 161.759 455.794 C 162.291 447.329 162.683 442.73 161.662 434.383 C 213.477 429.408 223.054 418.438 227.159 367.788 z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 762 B |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M5.9010,12.0 L8.9994,8.4976 A0.6,0.6 0 0,0 8.1006,7.7024 L4.5919,11.6690 A0.5,0.5 0 0,0 4.5919,12.3310 L8.1006,16.2976 A0.6,0.6 0 0,0 8.9994,15.5024 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M18.0990,12.0 L15.0006,8.4976 A0.6,0.6 0 0,1 15.8994,7.7024 L19.4081,11.6690 A0.5,0.5 0 0,1 19.4081,12.3310 L15.8994,16.2976 A0.6,0.6 0 0,1 15.0006,15.5024 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M13.7387,6.2657 L11.4387,17.9657 A0.6,0.6 0 0,1 10.2613,17.7343 L12.5613,6.0343 A0.6,0.6 0 0,1 13.7387,6.2657 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 800 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / menu_sidebar1</title>
|
||||
<g id="General-/-menu_sidebar1" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M51,11.75 C57.7654882,11.75 63.25,17.2345118 63.25,24 L63.25,48 C63.25,54.7654882 57.7654882,60.25 51,60.25 L21,60.25 C14.2345118,60.25 8.75,54.7654882 8.75,48 L8.75,24 C8.75,17.2345118 14.2345118,11.75 21,11.75 L51,11.75 Z M33.75,16.25 L21,16.25 C16.7197932,16.25 13.25,19.7197932 13.25,24 L13.25,48 C13.25,52.2802068 16.7197932,55.75 21,55.75 L33.75,55.75 L33.75,16.25 Z M51,16.25 L38.25,16.25 L38.25,55.75 L51,55.75 C55.2802068,55.75 58.75,52.2802068 58.75,48 L58.75,24 C58.75,19.7197932 55.2802068,16.25 51,16.25 Z M27,38.75 C28.2426407,38.75 29.25,39.7573593 29.25,41 C29.25,42.2426407 28.2426407,43.25 27,43.25 L20,43.25 C18.7573593,43.25 17.75,42.2426407 17.75,41 C17.75,39.7573593 18.7573593,38.75 20,38.75 L27,38.75 Z M27,30.25 C28.2426407,30.25 29.25,31.2573593 29.25,32.5 C29.25,33.7426407 28.2426407,34.75 27,34.75 L20,34.75 C18.7573593,34.75 17.75,33.7426407 17.75,32.5 C17.75,31.2573593 18.7573593,30.25 20,30.25 L27,30.25 Z M27,21.75 C28.2426407,21.75 29.25,22.7573593 29.25,24 C29.25,25.2426407 28.2426407,26.25 27,26.25 L20,26.25 C18.7573593,26.25 17.75,25.2426407 17.75,24 C17.75,22.7573593 18.7573593,21.75 20,21.75 L27,21.75 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / menu_sidebar2</title>
|
||||
<g id="General-/-menu_sidebar2" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M51,11.75 C57.7654882,11.75 63.25,17.2345118 63.25,24 L63.25,48 C63.25,54.7654882 57.7654882,60.25 51,60.25 L21,60.25 C14.2345118,60.25 8.75,54.7654882 8.75,48 L8.75,24 C8.75,17.2345118 14.2345118,11.75 21,11.75 L51,11.75 Z M58.75,40.249 L13.25,40.249 L13.25,48 C13.25,52.2802068 16.7197932,55.75 21,55.75 L51,55.75 C55.2802068,55.75 58.75,52.2802068 58.75,48 L58.75,40.249 Z M52,45.25 C53.2426407,45.25 54.25,46.2573593 54.25,47.5 C54.25,48.7426407 53.2426407,49.75 52,49.75 L47,49.75 C45.7573593,49.75 44.75,48.7426407 44.75,47.5 C44.75,46.2573593 45.7573593,45.25 47,45.25 L52,45.25 Z M38,45.25 C39.2426407,45.25 40.25,46.2573593 40.25,47.5 C40.25,48.7426407 39.2426407,49.75 38,49.75 L33,49.75 C31.7573593,49.75 30.75,48.7426407 30.75,47.5 C30.75,46.2573593 31.7573593,45.25 33,45.25 L38,45.25 Z M25,45.25 C26.2426407,45.25 27.25,46.2573593 27.25,47.5 C27.25,48.7426407 26.2426407,49.75 25,49.75 L20,49.75 C18.7573593,49.75 17.75,48.7426407 17.75,47.5 C17.75,46.2573593 18.7573593,45.25 20,45.25 L25,45.25 Z M51,16.25 L21,16.25 L20.2076076,16.2900124 C16.2996229,16.6868898 13.25,19.9873061 13.25,24 L13.25,35.749 L58.75,35.749 L58.75,24 C58.75,19.7197932 55.2802068,16.25 51,16.25 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / menu_sidebar3</title>
|
||||
<g id="General-/-menu_sidebar3" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M51,11.75 C57.7654882,11.75 63.25,17.2345118 63.25,24 L63.25,48 C63.25,54.7654882 57.7654882,60.25 51,60.25 L21,60.25 C14.2345118,60.25 8.75,54.7654882 8.75,48 L8.75,24 C8.75,17.2345118 14.2345118,11.75 21,11.75 L51,11.75 Z M58.75,36.249 L13.25,36.249 L13.25,48 C13.25,52.2802068 16.7197932,55.75 21,55.75 L51,55.75 C55.2802068,55.75 58.75,52.2802068 58.75,48 L58.75,36.249 Z M51,16.25 L21,16.25 L20.2076076,16.2900124 C16.2996229,16.6868898 13.25,19.9873061 13.25,24 L13.25,31.749 L58.75,31.749 L58.75,24 C58.75,19.7197932 55.2802068,16.25 51,16.25 Z M52,22.25 C53.2426407,22.25 54.25,23.2573593 54.25,24.5 C54.25,25.7426407 53.2426407,26.75 52,26.75 L47,26.75 C45.7573593,26.75 44.75,25.7426407 44.75,24.5 C44.75,23.2573593 45.7573593,22.25 47,22.25 L52,22.25 Z M38,22.25 C39.2426407,22.25 40.25,23.2573593 40.25,24.5 C40.25,25.7426407 39.2426407,26.75 38,26.75 L33,26.75 C31.7573593,26.75 30.75,25.7426407 30.75,24.5 C30.75,23.2573593 31.7573593,22.25 33,22.25 L38,22.25 Z M25,22.25 C26.2426407,22.25 27.25,23.2573593 27.25,24.5 C27.25,25.7426407 26.2426407,26.75 25,26.75 L20,26.75 C18.7573593,26.75 17.75,25.7426407 17.75,24.5 C17.75,23.2573593 18.7573593,22.25 20,22.25 L25,22.25 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_chatlist_mention</title>
|
||||
<g id="Filled-/-filled_chatlist_mention" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M36,7.94444444 C51.4946555,7.94444444 64.0555556,20.5053445 64.0555556,36 C64.0555556,45.4079588 60.5708092,50.852875 53.5366724,50.4132415 C50.1034854,50.1986673 47.6130345,48.761699 46.074269,46.3154879 C43.5446485,48.7868521 40.1096404,50.3348637 36.3135543,50.4158502 L36,50.4191919 C28.0365002,50.4191919 21.5808081,43.9634998 21.5808081,36 C21.5808081,28.0365002 28.0365002,21.5808081 36,21.5808081 C43.9634998,21.5808081 50.4191919,28.0365002 50.4191919,36 L50.4156549,36.3225752 C50.4221637,36.4530489 50.4193336,36.5855158 50.4077048,36.7192471 C49.9335852,42.1716224 51.0798543,44.1366551 53.917873,44.3140313 C56.580706,44.4804583 57.9444444,42.349617 57.9444444,36 C57.9444444,23.880418 48.119582,14.0555556 36,14.0555556 C23.880418,14.0555556 14.0555556,23.880418 14.0555556,36 C14.0555556,48.119582 23.880418,57.9444444 36,57.9444444 L46.4545455,57.9444444 C48.1420822,57.9444444 49.510101,59.3124633 49.510101,61 C49.510101,62.6875367 48.1420822,64.0555556 46.4545455,64.0555556 L36,64.0555556 C20.5053445,64.0555556 7.94444444,51.4946555 7.94444444,36 C7.94444444,20.5053445 20.5053445,7.94444444 36,7.94444444 Z M36,27.6919192 C31.4115737,27.6919192 27.6919192,31.4115737 27.6919192,36 C27.6919192,40.5884263 31.4115737,44.3080808 36,44.3080808 C40.5884263,44.3080808 44.3080808,40.5884263 44.3080808,36 C44.3080808,31.4115737 40.5884263,27.6919192 36,27.6919192 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.7 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_chatlist_poll</title>
|
||||
<g id="Filled-/-filled_chatlist_poll" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M16,27.6666667 C19.3137085,27.6666667 22,30.3529582 22,33.6666667 L22,55 C22,58.3137085 19.3137085,61 16,61 C12.6862915,61 10,58.3137085 10,55 L10,33.6666667 C10,30.4282697 12.5655749,27.7890949 15.7750617,27.6708051 L16,27.6666667 Z M36,11 C39.3137085,11 42,13.6862915 42,17 L42,55 C42,58.3137085 39.3137085,61 36,61 C32.6862915,61 30,58.3137085 30,55 L30,17 C30,13.6862915 32.6862915,11 36,11 Z M56,36 C59.3137085,36 62,38.6862915 62,42 L62,55 C62,58.3137085 59.3137085,61 56,61 C52.6862915,61 50,58.3137085 50,55 L50,42 C50,38.6862915 52.6862915,36 56,36 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 949 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_chatlist_reaction</title>
|
||||
<g id="Filled-/-filled_chatlist_reaction" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M39.7777796,60.0291132 C37.6505975,61.9950186 34.3758567,61.9950185 32.2486747,60.0006218 L31.9407931,59.7157078 C17.2464435,46.1823011 7.64613503,36.9178019 8.00999511,25.8631454 C8.17793054,21.0196104 10.6129942,16.375515 14.5594767,13.6403423 C21.5930174,8.75871019 30.1988776,13.0872447 35.2594851,18.4794853 C35.515351,18.7521185 36.4762456,18.7597927 36.7283013,18.4906892 C41.7874742,13.0893374 50.4004979,8.72813558 57.4389884,13.6403423 C61.3854708,16.375515 63.8205345,21.0196104 63.9884699,25.8631454 C64.3803192,36.9178019 54.7520216,46.1823011 40.0576719,59.7726906 L39.7777796,60.0291132 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1000 B |
|
Depois Largura: | Altura: | Tamanho: 395 B |
|
Depois Largura: | Altura: | Tamanho: 541 B |
|
Depois Largura: | Altura: | Tamanho: 653 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<rect x="11" y="11" width="18" height="18" rx="3.5" stroke="#FFFFFF" stroke-width="1.5" fill="none"/>
|
||||
<g transform="translate(20,20) scale(0.19) translate(-36,-36)">
|
||||
<path d="M16,27.6666667 C19.3137085,27.6666667 22,30.3529582 22,33.6666667 L22,55 C22,58.3137085 19.3137085,61 16,61 C12.6862915,61 10,58.3137085 10,55 L10,33.6666667 C10,30.4282697 12.5655749,27.7890949 15.7750617,27.6708051 L16,27.6666667 Z M36,11 C39.3137085,11 42,13.6862915 42,17 L42,55 C42,58.3137085 39.3137085,61 36,61 C32.6862915,61 30,58.3137085 30,55 L30,17 C30,13.6862915 32.6862915,11 36,11 Z M56,36 C59.3137085,36 62,38.6862915 62,42 L62,55 C62,58.3137085 59.3137085,61 56,61 C52.6862915,61 50,58.3137085 50,55 L50,42 C50,38.6862915 52.6862915,36 56,36 Z" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1005 B |
|
Depois Largura: | Altura: | Tamanho: 1.4 KiB |
|
Depois Largura: | Altura: | Tamanho: 2.2 KiB |
|
Depois Largura: | Altura: | Tamanho: 3.1 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M4.551472,18.848528 Q4.551472,19.448528 5.151472,19.448528 L8.576955,19.448528 Q9.076955,19.448528 9.430509,19.094975 L19.202082,9.323402 Q20.262742,8.262742 19.202082,7.202082 L16.797918,4.797918 Q15.737258,3.737258 14.676598,4.797918 L4.905025,14.569491 Q4.551472,14.923045 4.551472,15.423045 Z M5.751472,18.248528 L5.751472,15.420101 L12.838120,8.333452 L15.666548,11.161880 L8.579899,18.248528 Z M13.686649,7.484924 L15.171573,6.000000 Q15.737258,5.434315 16.302944,6.000000 L18.000000,7.697056 Q18.565685,8.262742 18.000000,8.828427 L16.515076,10.313351 Z" fill="#FFFFFF"></path>
|
||||
<path d="M4.5,7.3999999999999995 L5.2,8.9 L6.7,9.6 L5.2,10.299999999999999 L4.5,11.8 L3.8,10.299999999999999 L2.3,9.6 L3.8,8.9 Z" fill="#FFFFFF"></path>
|
||||
<path d="M9.3,2.45 L9.975000000000001,3.7250000000000005 L11.25,4.4 L9.975000000000001,5.075 L9.3,6.3500000000000005 L8.625,5.075 L7.3500000000000005,4.4 L8.625,3.7250000000000005 Z" fill="#FFFFFF"></path>
|
||||
<path d="M17.7,16.3 L18.4,17.8 L19.9,18.5 L18.4,19.2 L17.7,20.7 L17.0,19.2 L15.5,18.5 L17.0,17.8 Z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.3 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M7.142857,5.4 L16.857143,5.4 C19.068542,5.4 20.857143,7.188601 20.857143,9.4 L20.857143,14.6 C20.857143,16.811399 19.068542,18.6 16.857143,18.6 L7.142857,18.6 C4.931458,18.6 3.142857,16.811399 3.142857,14.6 L3.142857,9.4 C3.142857,7.188601 4.931458,5.4 7.142857,5.4 Z M7.142857,6.6 C5.594716,6.6 4.342857,7.851859 4.342857,9.4 L4.342857,14.6 C4.342857,16.148141 5.594716,17.4 7.142857,17.4 L16.857143,17.4 C18.405284,17.4 19.657143,16.148141 19.657143,14.6 L19.657143,9.4 C19.657143,7.851859 18.405284,6.6 16.857143,6.6 L7.142857,6.6 Z M6.925,8.85 L8.225,8.85 L8.225,11.4 L10.225,11.4 L10.225,8.85 L11.525,8.85 L11.525,15.15 L10.225,15.15 L10.225,12.6 L8.225,12.6 L8.225,15.15 L6.925,15.15 L6.925,8.85 Z M12.725,8.85 L14.725,8.85 C16.466907,8.85 17.875,10.258093 17.875,12.0 C17.875,13.741907 16.466907,15.15 14.725,15.15 L12.725,15.15 L12.725,8.85 Z M14.025,10.05 L14.025,13.95 L14.725,13.95 C15.802321,13.95 16.675,13.077321 16.675,12.0 C16.675,10.922679 15.802321,10.05 14.725,10.05 L14.025,10.05 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.2 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M10.4,3.4 C14.2660068,3.4 17.4,6.53399322 17.4,10.4 C17.4,12.0738498 16.8191498,13.6082996 15.8486249,14.8118249 L15.8486249,14.8118249 L21.0183712,19.9815712 C21.2527634,20.2159634 21.2527634,20.5960366 21.0183712,20.8304288 L20.8304288,21.0183712 C20.5960366,21.2527634 20.2159634,21.2527634 19.9815712,21.0183712 L19.9815712,21.0183712 L14.8118249,15.8486249 C13.6082996,16.8191498 12.0738498,17.4 10.4,17.4 C6.53399322,17.4 3.4,14.2660068 3.4,10.4 C3.4,6.53399322 6.53399322,3.4 10.4,3.4 Z M10.4,4.6 C7.19644661,4.6 4.6,7.19644661 4.6,10.4 C4.6,13.6035534 7.19644661,16.2 10.4,16.2 C13.6035534,16.2 16.2,13.6035534 16.2,10.4 C16.2,7.19644661 13.6035534,4.6 10.4,4.6 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
<path d="M13.2778175,8.12781746 C13.5121903,7.89344461 13.8922635,7.89344461 14.1266363,8.12781746 C14.3610092,8.3622103 14.3610092,8.74228354 14.1266363,8.97667638 L14.1266363,8.97667638 L9.92663635,13.1766764 C9.69226351,13.4110492 9.31219027,13.4110492 9.07781742,13.1766764 L9.07781742,13.1766764 L7.27781742,11.3766764 C7.04344458,11.1423035 7.04344458,10.7622303 7.27781742,10.5278575 C7.51219027,10.2934846 7.89226351,10.2934846 8.12663635,10.5278575 L8.12663635,10.5278575 L9.50222689,11.9034481 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.5 KiB |
|
Antes Largura: | Altura: | Tamanho: 797 B |
|
Antes Largura: | Altura: | Tamanho: 1.4 KiB |
|
Antes Largura: | Altura: | Tamanho: 2.1 KiB |
|
Depois Largura: | Altura: | Tamanho: 652 B |
|
Depois Largura: | Altura: | Tamanho: 1.4 KiB |
|
Depois Largura: | Altura: | Tamanho: 2.0 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 21 21">
|
||||
<g fill="none" fill-rule="evenodd" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" transform="translate(2 2)">
|
||||
<path d="m16.5 14.5v-12c0-1.1045695-.8954305-2-2-2h-12c-1.1045695 0-2 .8954305-2 2v12c0 1.1045695.8954305 2 2 2h12c1.1045695 0 2-.8954305 2-2z"/>
|
||||
<path d="m.5 12.5h10c1.1045695 0 2-.8954305 2-2v-10"/>
|
||||
<path d="m.5 8.5h7c.55228475 0 1-.44771525 1-1v-7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 494 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_add</title>
|
||||
<g id="Filled-/-filled_poll_add" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M36,11 C49.8071187,11 61,22.1928813 61,36 C61,49.8071187 49.8071187,61 36,61 C22.1928813,61 11,49.8071187 11,36 C11,22.1928813 22.1928813,11 36,11 Z M36,21.8333333 C34.6192881,21.8333333 33.5,22.9526215 33.5,24.3333333 L33.4993333,33.4993333 L24.3333333,33.5 C22.9526215,33.5 21.8333333,34.6192881 21.8333333,36 C21.8333333,37.3807119 22.9526215,38.5 24.3333333,38.5 L33.4993333,38.4993333 L33.5,47.6666667 C33.5,49.0473785 34.6192881,50.1666667 36,50.1666667 C37.3807119,50.1666667 38.5,49.0473785 38.5,47.6666667 L38.4993333,38.4993333 L47.6666667,38.5 C49.0473785,38.5 50.1666667,37.3807119 50.1666667,36 C50.1666667,34.6192881 49.0473785,33.5 47.6666667,33.5 L38.4993333,33.4993333 L38.5,24.3333333 C38.5,22.9526215 37.3807119,21.8333333 36,21.8333333 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_correct</title>
|
||||
<g id="Filled-/-filled_poll_correct" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M47.2,12 C54.2692448,12 60,17.7307552 60,24.8 L60,47.2 C60,54.2692448 54.2692448,60 47.2,60 L24.8,60 C17.7307552,60 12,54.2692448 12,47.2 L12,24.8 C12,17.7307552 17.7307552,12 24.8,12 L47.2,12 Z M49.6564101,26.3327574 C48.6649568,25.3557475 47.0574935,25.3557475 46.0660402,26.3327574 L32.2666156,43.6360554 L25.9339598,37.3958969 C24.9425065,36.418887 23.3350432,36.418887 22.3435899,37.3958969 C21.3521367,38.3729068 21.3521367,39.9569529 22.3435899,40.9339628 L30.3390683,48.8129642 C31.4039625,49.8623453 33.1304972,49.8623453 34.1953915,48.8129642 L49.6564101,29.8708233 C50.6478633,28.8938134 50.6478633,27.3097674 49.6564101,26.3327574 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.0 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_deadline</title>
|
||||
<g id="Filled-/-filled_poll_deadline" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M30.8333333,13.952381 C30.0305556,13.952381 29.3576389,13.664881 28.8145833,13.089881 C28.2715278,12.514881 28,11.802381 28,10.952381 C28,10.102381 28.2715278,9.38988095 28.8145833,8.81488095 C29.3576389,8.23988095 30.0305556,7.95238095 30.8333333,7.95238095 L42.1666667,7.95238095 C42.9694444,7.95238095 43.6423611,8.23988095 44.1854167,8.81488095 C44.7284722,9.38988095 45,10.102381 45,10.952381 C45,11.802381 44.7284722,12.514881 44.1854167,13.089881 C43.6423611,13.664881 42.9694444,13.952381 42.1666667,13.952381 L30.8333333,13.952381 Z M36.5,16.4285714 C39.0833333,16.4285714 41.5625,16.8412698 43.9375,17.6666667 C46.3125,18.4920635 48.5416667,19.6888889 50.625,21.2571429 L52.6433105,19.2929687 C53.1016439,18.8390005 54.907665,17.7189424 56.4968262,19.2929687 C58.0859874,20.8669951 56.9551595,22.8330783 56.4968262,23.2870466 L54.4785156,25.2512207 C56.061849,27.3147128 56.9166667,28.9952381 57.75,31.347619 C58.5833333,33.7 59,36.1555556 59,38.7142857 C59,41.768254 58.40625,44.6468254 57.21875,47.35 C56.03125,50.0531746 54.4166667,52.415873 52.375,54.4380952 C50.3333333,56.4603175 47.9479167,58.0595238 45.21875,59.2357143 C42.4895833,60.4119048 39.5833333,61 36.5,61 C33.4166667,61 30.5104167,60.4119048 27.78125,59.2357143 C25.0520833,58.0595238 22.6666667,56.4603175 20.625,54.4380952 C18.5833333,52.415873 16.96875,50.0531746 15.78125,47.35 C14.59375,44.6468254 14,41.768254 14,38.7142857 C14,35.6603175 14.59375,32.781746 15.78125,30.0785714 C16.96875,27.3753968 18.5833333,25.0126984 20.625,22.9904762 C22.6666667,20.968254 25.0520833,19.3690476 27.78125,18.1928571 C30.5104167,17.0166667 33.4166667,16.4285714 36.5,16.4285714 Z M36.5,25 C35.65,25 34.9375,25.2715278 34.3625,25.8145833 C33.7875,26.3576389 33.5,27.0305556 33.5,27.8333333 L33.5,39.1666667 C33.5,39.9694444 33.7875,40.6423611 34.3625,41.1854167 C34.9375,41.7284722 35.65,42 36.5,42 C37.35,42 38.0625,41.7284722 38.6375,41.1854167 C39.2125,40.6423611 39.5,39.9694444 39.5,39.1666667 L39.5,27.8333333 C39.5,27.0305556 39.2125,26.3576389 38.6375,25.8145833 C38.0625,25.2715278 37.35,25 36.5,25 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_multiple</title>
|
||||
<g id="Filled-/-filled_poll_multiple" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M16.5555556,17.4909091 L24.8888889,17.4909091 C27.9571375,17.4909091 30.4444444,19.978216 30.4444444,23.0464646 L30.4444444,33.2599327 C30.4444444,35.3054317 28.7862398,36.9636364 26.7407407,36.9636364 L16.5555556,36.9636364 C13.4873069,36.9636364 11,34.4763294 11,31.4080808 L11,23.0464646 C11,20.0512696 13.3702738,17.6096816 16.3374087,17.4951135 L16.5555556,17.4909091 Z M38.7777778,42.5272727 L48.962963,42.5272727 C52.0312116,42.5272727 54.5185185,45.0145797 54.5185185,48.0828283 L54.5185185,56.4444444 C54.5185185,59.5126931 52.0312116,62 48.962963,62 L40.6296296,62 C37.561381,62 35.0740741,59.5126931 35.0740741,56.4444444 L35.0740741,46.2309764 C35.0740741,44.1854774 36.7322787,42.5272727 38.7777778,42.5272727 Z M53.5925926,11 C57.6835907,11 61,14.3164093 61,18.4074074 L61,29.5185185 C61,33.6095167 57.6835907,36.9259259 53.5925926,36.9259259 L40.6296296,36.9259259 C37.561381,36.9259259 35.0740741,34.438619 35.0740741,31.3703704 L35.0740741,18.4074074 C35.0740741,14.3164093 38.3904833,11 42.4814815,11 L53.5925926,11 Z M55.6874997,18.2754929 C54.8942761,17.6298824 53.7278705,17.7495465 53.0822601,18.5427701 L45.961881,27.2911613 L42.6411074,23.4457067 C41.9726527,22.6716364 40.803255,22.586018 40.0291847,23.2544728 C39.2551144,23.9229275 39.169496,25.0923251 39.8379508,25.8663954 L44.5301306,31.2999379 C45.232008,32.1127117 46.4598755,32.202611 47.2726494,31.5007335 L47.3783678,31.402175 L47.5098592,31.2565065 L55.954777,20.8807325 C56.6003874,20.0875088 56.4807233,18.9211033 55.6874997,18.2754929 Z M18.4074074,42.5272727 L25.8148148,42.5272727 C27.8603139,42.5272727 29.5185185,44.1854774 29.5185185,46.2309764 L29.5185185,53.6612795 C29.5185185,56.2181533 27.4457627,58.2909091 24.8888889,58.2909091 L18.4074074,58.2909091 C15.8505336,58.2909091 13.7777778,56.2181533 13.7777778,53.6612795 L13.7777778,47.1569024 C13.7777778,44.6710528 15.7369798,42.6427994 18.1954893,42.5320367 L18.4074074,42.5272727 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.3 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_revote</title>
|
||||
<g id="Filled-/-filled_poll_revote" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M16.7507988,28.8661257 C16.8389425,28.9366406 16.919004,29.0167022 16.9895189,29.1048458 L24.5984627,38.6160256 C24.8621457,38.9456293 24.8087064,39.4265832 24.4791027,39.6902661 C24.3435863,39.7986792 24.175208,39.8577431 24.0016625,39.8577431 L18.4910881,39.8576308 C20.2399531,48.0322807 27.4321212,54.15 36.0284229,54.15 C40.7008203,54.15 45.0859067,52.3395591 48.4025538,49.1436671 C49.5359903,48.0514976 51.3402004,48.0849512 52.4323698,49.2183877 C53.5245392,50.3518242 53.4910857,52.1560342 52.3576492,53.2482037 C47.9904523,57.4563963 42.1928815,59.85 36.0284229,59.85 C24.267193,59.85 14.5200422,51.1906554 12.6927481,39.8571048 L7.59017428,39.8577431 C7.1680756,39.8577431 6.82589701,39.5155645 6.82589701,39.0934658 C6.82589701,38.9199202 6.88496093,38.7515419 6.993374,38.6160256 L14.6023178,29.1048458 C15.1296837,28.4456385 16.0915914,28.3387598 16.7507988,28.8661257 Z M36.0284229,12.15 C47.9315923,12.15 57.7717756,21.0196149 59.4267845,32.5543871 L64.4098257,32.5540216 C64.5833713,32.5540216 64.7517496,32.6130855 64.8872659,32.7214986 C65.2168696,32.9851816 65.270309,33.4661354 65.006626,33.7957391 L57.3976822,43.3069189 C57.3271673,43.3950625 57.2471057,43.4751241 57.1589621,43.545639 C56.4997547,44.0730049 55.537847,43.9661263 55.0104811,43.3069189 L47.4015373,33.7957391 C47.2931242,33.6602228 47.2340603,33.4918445 47.2340603,33.3182989 C47.2340603,32.8962002 47.5762389,32.5540216 47.9983375,32.5540216 L53.648921,32.5542434 C52.0570804,24.1740704 44.7684992,17.85 36.0284229,17.85 C31.1883177,17.85 26.6584709,19.7937975 23.3100187,23.1970372 C22.2060923,24.319026 20.4016315,24.3336685 19.2796427,23.2297422 C18.1576539,22.1258158 18.1430114,20.321355 19.2469377,19.1993662 C23.6547728,14.71941 29.6425538,12.15 36.0284229,12.15 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_shuffle</title>
|
||||
<g id="Filled-/-filled_poll_shuffle" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M21.5,11.5 C27.0228475,11.5 31.5,15.9771525 31.5,21.5 C31.5,26.9292399 27.1733292,31.3479317 21.7799048,31.4961582 L21.5,31.5 C15.9771525,31.5 11.5,27.0228475 11.5,21.5 C11.5,15.9771525 15.9771525,11.5 21.5,11.5 Z M50.5,40.3888889 C56.0228475,40.3888889 60.5,44.8660414 60.5,50.3888889 C60.5,55.8181288 56.1733292,60.2368206 50.7799048,60.3850471 L50.5,60.3888889 C44.9771525,60.3888889 40.5,55.9117364 40.5,50.3888889 C40.5,44.8660414 44.9771525,40.3888889 50.5,40.3888889 Z M22.786139,36.3751698 L30.4647439,44.0537747 C31.5191595,45.1081902 31.5191595,46.8177357 30.4647439,47.8721513 C29.4103283,48.9265669 27.7007828,48.9265669 26.6463672,47.8721513 L22.9218889,44.148 L22.9222222,51.5185185 C22.9222222,52.5842235 23.7861468,53.4481481 24.8518519,53.4481481 L32.2592593,53.4481481 C33.7504281,53.4481481 34.9592593,54.6569793 34.9592593,56.1481481 C34.9592593,57.639317 33.7504281,58.8481481 32.2592593,58.8481481 L24.8518519,58.8481481 C20.8038092,58.8481481 17.5222222,55.5665612 17.5222222,51.5185185 L17.5218889,44.148 L13.7980772,47.8721513 C12.7436616,48.9265669 11.0341162,48.9265669 9.97970058,47.8721513 C8.92528499,46.8177357 8.92528499,45.1081902 9.97970058,44.0537747 L17.6583054,36.3751698 C19.0743176,34.9591577 21.3701269,34.9591577 22.786139,36.3751698 Z M46.1481481,11.9296296 C50.1961908,11.9296296 53.4777778,15.2112166 53.4777778,19.2592593 L53.4768889,26.629 L57.2019228,22.9056265 C58.2563384,21.8512109 59.9658838,21.8512109 61.0202994,22.9056265 C62.074715,23.9600421 62.074715,25.6695875 61.0202994,26.7240031 L53.3416946,34.402608 C51.9256824,35.8186201 49.6298731,35.8186201 48.213861,34.402608 L40.5352561,26.7240031 C39.4808405,25.6695875 39.4808405,23.9600421 40.5352561,22.9056265 C41.5896717,21.8512109 43.2992172,21.8512109 44.3536328,22.9056265 L48.0768889,26.629 L48.0777778,19.2592593 C48.0777778,18.1935542 47.2138532,17.3296296 46.1481481,17.3296296 L38.7407407,17.3296296 C37.2495719,17.3296296 36.0407407,16.1207985 36.0407407,14.6296296 C36.0407407,13.1384608 37.2495719,11.9296296 38.7407407,11.9296296 L46.1481481,11.9296296 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Filled / filled_poll_view</title>
|
||||
<g id="Filled-/-filled_poll_view" stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M36,14 C50.34375,14 63,28.6467438 63,35.5 C63,42.3532562 50.34375,57 36,57 C21.65625,57 9,42.2272172 9,35.5 C9,28.7727828 21.65625,14 36,14 Z M36,21.1666667 C28.2680135,21.1666667 22,27.5839186 22,35.5 C22,43.4160814 28.2680135,49.8333333 36,49.8333333 C43.7319865,49.8333333 50,43.4160814 50,35.5 C50,27.5839186 43.7319865,21.1666667 36,21.1666667 Z M36,26.2857143 C40.9705627,26.2857143 45,30.4110905 45,35.5 C45,40.5889095 40.9705627,44.7142857 36,44.7142857 C31.0294373,44.7142857 27,40.5889095 27,35.5 C27,30.4110905 31.0294373,26.2857143 36,26.2857143 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 941 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / menu_poll_order</title>
|
||||
<g id="General-/-menu_poll_order" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M59.25,22 C59.25,23.2426407 58.2426407,24.25 57,24.25 L15,24.25 C13.7573593,24.25 12.75,23.2426407 12.75,22 C12.75,20.7573593 13.7573593,19.75 15,19.75 L57,19.75 C58.2426407,19.75 59.25,20.7573593 59.25,22 Z M59.25,36.0001 C59.25,37.2427407 58.2426407,38.2501 57,38.2501 L15,38.2501 C13.7573593,38.2501 12.75,37.2427407 12.75,36.0001 C12.75,34.7574593 13.7573593,33.7501 15,33.7501 L57,33.7501 C58.2426407,33.7501 59.25,34.7574593 59.25,36.0001 Z M59.25,50.0002 C59.25,51.2428407 58.2426407,52.2502 57,52.2502 L15,52.2502 C13.7573593,52.2502 12.75,51.2428407 12.75,50.0002 C12.75,48.7575593 13.7573593,47.7502 15,47.7502 L57,47.7502 C58.2426407,47.7502 59.25,48.7575593 59.25,50.0002 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.0 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / outline_poll_add</title>
|
||||
<g id="General-/-outline_poll_add" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M36,14.6 C37.3254834,14.6 38.4,15.6745166 38.4,17 L38.4,33.599 L55,33.6 C56.3254834,33.6 57.4,34.6745166 57.4,36 C57.4,37.3254834 56.3254834,38.4 55,38.4 L38.4,38.399 L38.4,55 C38.4,56.3254834 37.3254834,57.4 36,57.4 C34.6745166,57.4 33.6,56.3254834 33.6,55 L33.6,38.399 L17,38.4 C15.6745166,38.4 14.6,37.3254834 14.6,36 C14.6,34.6745166 15.6745166,33.6 17,33.6 L33.6,33.599 L33.6,17 C33.6,15.6745166 34.6745166,14.6 36,14.6 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 809 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / outline_poll_attach</title>
|
||||
<g id="General-/-outline_poll_attach" stroke="none" fill="none" stroke-linecap="round" stroke-width="4.7">
|
||||
<path d="M15.238686,27.5983631 L27.7824856,15.0545635 C35.007185,7.82986412 46.7207371,7.82986412 53.9454365,15.0545635 C61.1701359,22.2792629 61.1701359,33.992815 53.9454365,41.2175144 L37.3646961,57.7982548 C32.2678495,62.8951014 24.0042284,62.8951014 18.9073818,57.7982548 C13.9035223,52.7943953 13.9035223,44.681536 18.9073818,39.6776765 C18.9629866,39.6220717 19.0191022,39.5669799 19.0757213,39.5124082 L36.1473675,23.0580944 C38.8308397,20.47166 43.0808084,20.4755332 45.7595618,23.0668544 C48.3005825,25.5249383 48.367816,29.5775132 45.9097321,32.1185338 C45.8463292,32.1840759 45.7815331,32.2482557 45.7153881,32.3110293 L28.3704571,48.7718787 L28.3704571,48.7718787" id="Attach" stroke="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / outline_poll_emoji</title>
|
||||
<g id="General-/-outline_poll_emoji" stroke="none" fill="none" fill-rule="nonzero">
|
||||
<path d="M36,7 C52.0162577,7 65,19.9837423 65,36 C65,52.0162577 52.0162577,65 36,65 C19.9837423,65 7,52.0162577 7,36 C7,19.9837423 19.9837423,7 36,7 Z M36,11.7 C22.5794806,11.7 11.7,22.5794806 11.7,36 C11.7,49.4205194 22.5794806,60.3 36,60.3 C49.4205194,60.3 60.3,49.4205194 60.3,36 C60.3,22.5794806 49.4205194,11.7 36,11.7 Z M48.8529415,40.9299989 C49.8673051,41.6219793 50.1286483,43.0052446 49.4366678,44.0196082 C46.3560807,48.5353937 41.3624832,51.2849169 35.9183931,51.2849169 C30.5877028,51.2849169 25.6851705,48.6491709 22.5874806,44.2887303 C21.8763436,43.2877035 22.1113457,41.8997205 23.1123725,41.1885834 C24.1133994,40.4774464 25.5013824,40.7124485 26.2125194,41.7134754 C28.4917077,44.9217582 32.0564155,46.8382502 35.9183931,46.8382502 C39.8628867,46.8382502 43.4955082,44.8380936 45.7633322,41.5137251 C46.4553127,40.4993615 47.8385779,40.2380184 48.8529415,40.9299989 Z M27.3,26.5266667 C29.1685634,26.5266667 30.6833333,28.474228 30.6833333,30.8766667 C30.6833333,33.2791053 29.1685634,35.2266667 27.3,35.2266667 C25.4314366,35.2266667 23.9166667,33.2791053 23.9166667,30.8766667 C23.9166667,28.474228 25.4314366,26.5266667 27.3,26.5266667 Z M44.7,26.5266667 C46.5685634,26.5266667 48.0833333,28.474228 48.0833333,30.8766667 C48.0833333,33.2791053 46.5685634,35.2266667 44.7,35.2266667 C42.8314366,35.2266667 41.3166667,33.2791053 41.3166667,30.8766667 C41.3166667,28.474228 42.8314366,26.5266667 44.7,26.5266667 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.8 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Icon / SendMedia / cross</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M8.2,7.2 L7.2,8.2 L10.99,12 L7.2,15.8 L8.2,16.8 L12,13.01 L15.8,16.8 L16.8,15.8 L13.01,12 L16.8,8.2 L15.8,7.2 L12,10.99 L8.2,7.2 Z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 444 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Icon / SendMedia / more_vertical</title>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<circle fill="#FFFFFF" cx="12" cy="6.5" r="1.5"></circle>
|
||||
<circle fill="#FFFFFF" cx="12" cy="12" r="1.5"></circle>
|
||||
<circle fill="#FFFFFF" cx="12" cy="17.5" r="1.5"></circle>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 478 B |
|
Antes Largura: | Altura: | Tamanho: 605 B Depois Largura: | Altura: | Tamanho: 605 B |
|
Antes Largura: | Altura: | Tamanho: 1.1 KiB Depois Largura: | Altura: | Tamanho: 1.1 KiB |
|
Antes Largura: | Altura: | Tamanho: 1.7 KiB Depois Largura: | Altura: | Tamanho: 1.7 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="-48 0 1608 1608" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M 712.679 309.46 C 994.711 289.885 1239.18 502.726 1258.61 784.767 C 1278.04 1066.81 1065.08 1311.17 783.031 1330.46 C 501.179 1349.74 257.028 1136.96 237.608 855.119 C 218.188 573.276 430.846 329.02 712.679 309.46 z M 967.909 662.173 C 982.124 662.158 990.527 665.946 1001.08 675.338 C 1028.72 699.945 1017.83 725.703 994.865 748.755 C 974.663 769.038 953.972 788.738 933.545 808.699 L 810.286 929.421 L 740.428 998.187 C 725.888 1012.62 712.403 1027.09 696.606 1040.2 C 651.854 1077.33 617.094 1031.61 586.489 1001.07 L 527.05 942.312 C 508.931 924.753 471.682 895.7 479.446 867.346 C 485.714 839.343 512.97 826.729 539.672 834.932 C 557.533 840.419 587.863 875.346 602.47 889.516 C 620.486 906.993 646.727 929.858 658.81 950.449 C 667.709 940.687 681.671 929.352 691.853 919.157 L 813.593 799.487 L 894.751 719.881 C 913.806 701.234 942.629 667.935 967.909 662.173 z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.1 KiB |
|
Antes Largura: | Altura: | Tamanho: 504 B Depois Largura: | Altura: | Tamanho: 504 B |
|
Antes Largura: | Altura: | Tamanho: 992 B Depois Largura: | Altura: | Tamanho: 992 B |
|
Antes Largura: | Altura: | Tamanho: 1.5 KiB Depois Largura: | Altura: | Tamanho: 1.5 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24px" height="24px" viewBox="50 52 1295 1295" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="none" fill="none" fill-rule="evenodd">
|
||||
<path d="M 694.697 200.371 C 696.934 200.212 699.175 200.111 701.418 200.068 C 745.508 199.473 758.948 257.705 773.748 289.53 C 794.345 333.821 811.886 379.596 832.298 423.998 C 847.934 458.014 855.763 490.419 883.531 517.19 C 903.098 522.497 925.281 521.666 945.47 523.584 C 979.491 526.816 1013.21 532.577 1047.36 534.65 C 1094.94 537.537 1154.44 533.159 1200.16 546.144 C 1216.27 550.718 1226.72 557.474 1234.93 572.371 C 1243.74 588.375 1243.82 603.784 1234.78 619.805 C 1221.66 643.03 1140.48 703.874 1114.65 724.87 C 1095.71 740.263 1077.5 756.481 1058.31 771.588 C 1039.48 786.416 1003.87 810.195 992.189 831.093 C 986.938 840.49 987.593 854.519 988.781 864.808 C 991.499 888.351 999.881 912.529 1005.73 935.545 L 1036.08 1056.49 C 1044.45 1089.37 1059.34 1133.81 1051.47 1167.7 C 1048.56 1176.62 1038.01 1187.14 1030.2 1191.32 C 993.542 1210.99 954.764 1179.59 923.854 1161.35 L 803.61 1090.76 C 776.417 1074.72 745.323 1054.5 716.286 1043.26 C 684.275 1030.88 640.041 1065.58 612.382 1081.18 C 570.231 1104.96 528.855 1131.52 486.309 1154.92 C 470.832 1164.86 454.312 1174.51 438.356 1183.66 C 411.204 1199.23 394.957 1198.04 366.384 1190.13 C 362.056 1180.35 357.375 1175.82 348.47 1170.13 C 344.363 1128.2 354.192 1104.38 364.813 1064.99 C 381.211 1004.18 391.922 938.255 444.589 896.651 C 476.246 871.644 516.54 854.894 552.836 837.24 C 594.367 816.757 636.055 796.593 677.895 776.749 C 690.118 770.929 704.429 761.062 715.04 756.628 C 715.232 747.952 715.281 739.274 715.187 730.597 C 707.969 727.62 697.02 721.113 689.051 721.779 C 660.503 724.166 630.946 728.307 602.422 731.286 C 584.032 733.207 563.84 739.737 545.86 741.351 C 510.314 744.541 476.718 749.761 441.818 756.89 C 382.766 766.871 328.604 752.74 280.295 717.147 C 256.88 699.568 233.837 681.498 211.181 662.951 C 198.898 652.734 185.839 642.705 175.494 630.442 C 168.729 622.423 161.996 612.481 160.511 601.851 C 158.669 588.657 164.684 573.239 172.798 563.003 C 199.402 529.44 316.297 537.298 357.863 534.154 C 387.762 531.893 417.619 527.297 447.464 524.222 C 466.969 522.212 486.818 521.895 506.226 519.397 C 514.431 518.34 518.24 516.514 523.868 510.477 C 530.893 502.942 537.029 493.735 541.909 484.667 C 552.383 465.204 559.981 443.305 568.842 423.011 L 630.739 279.975 C 644.543 248.186 654.976 205.06 694.697 200.371 z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.5 KiB |
@@ -600,6 +600,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_notification_private_chats" = "Private chats";
|
||||
"lng_notification_groups" = "Groups";
|
||||
"lng_notification_channels" = "Channels";
|
||||
"lng_notification_reactions" = "Reactions";
|
||||
"lng_notification_reactions_title" = "Notifications for reactions";
|
||||
"lng_notification_reactions_notify_about" = "Notify me about";
|
||||
"lng_notification_reactions_messages" = "Messages";
|
||||
"lng_notification_reactions_messages_full" = "Reactions to my messages";
|
||||
"lng_notification_reactions_poll_votes" = "Poll votes";
|
||||
"lng_notification_reactions_poll_votes_full" = "Votes in my polls";
|
||||
"lng_notification_reactions_from" = "Notify about reactions from";
|
||||
"lng_notification_reactions_from_nobody" = "Off";
|
||||
"lng_notification_reactions_from_contacts" = "From my contacts";
|
||||
"lng_notification_reactions_from_all" = "From everyone";
|
||||
"lng_notification_reactions_settings" = "Settings";
|
||||
"lng_notification_reactions_show_sender" = "Show sender's name";
|
||||
"lng_notification_click_to_change" = "Click here to change";
|
||||
"lng_notification_on" = "On, {exceptions}";
|
||||
"lng_notification_off" = "Off, {exceptions}";
|
||||
@@ -647,6 +660,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reaction_invoice" = "{reaction} to your invoice";
|
||||
"lng_reaction_gif" = "{reaction} to your GIF";
|
||||
|
||||
"lng_poll_vote_option" = "voted for \"{option}\" in your poll";
|
||||
"lng_poll_vote" = "voted in your poll \"{title}\"";
|
||||
"lng_poll_vote_notext" = "voted in your poll";
|
||||
|
||||
"lng_effect_add_title" = "Add an animated effect";
|
||||
"lng_effect_stickers_title" = "Effects from stickers";
|
||||
"lng_effect_send" = "Send with Effect";
|
||||
@@ -695,6 +712,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_settings_section_chat_settings" = "Chat Settings";
|
||||
"lng_settings_replace_emojis" = "Replace emoji automatically";
|
||||
"lng_settings_system_text_replace" = "System text replacements";
|
||||
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
|
||||
"lng_settings_suggest_animated_emoji" = "Suggest animated emoji";
|
||||
"lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji";
|
||||
@@ -864,6 +882,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_theme_tinted" = "Tinted";
|
||||
"lng_settings_theme_night" = "Night";
|
||||
"lng_settings_theme_accent_title" = "Choose accent color";
|
||||
"lng_settings_theme_system_accent_color" = "System accent color";
|
||||
"lng_settings_data_storage" = "Data and storage";
|
||||
"lng_settings_information" = "Edit profile";
|
||||
"lng_settings_my_account" = "My Account";
|
||||
@@ -1654,6 +1673,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_photos#other" = "{count} photos";
|
||||
"lng_profile_gifs#one" = "{count} GIF";
|
||||
"lng_profile_gifs#other" = "{count} GIFs";
|
||||
"lng_profile_polls#one" = "{count} poll";
|
||||
"lng_profile_polls#other" = "{count} polls";
|
||||
"lng_profile_videos#one" = "{count} video";
|
||||
"lng_profile_videos#other" = "{count} videos";
|
||||
"lng_profile_songs#one" = "{count} audio file";
|
||||
@@ -1822,6 +1843,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_media_type_files" = "Files";
|
||||
"lng_media_type_audios" = "Voice messages";
|
||||
"lng_media_type_links" = "Shared links";
|
||||
"lng_media_type_polls" = "Polls";
|
||||
"lng_polls_search_none" = "No polls found";
|
||||
"lng_media_type_rounds" = "Video messages";
|
||||
"lng_media_saved_music_your" = "Your playlist";
|
||||
"lng_media_saved_music_title" = "Playlist";
|
||||
@@ -1850,6 +1873,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_media_selected_audio#other" = "{count} Voice messages";
|
||||
"lng_media_selected_link#one" = "{count} shared link";
|
||||
"lng_media_selected_link#other" = "{count} shared links";
|
||||
"lng_media_selected_poll#one" = "{count} Poll";
|
||||
"lng_media_selected_poll#other" = "{count} Polls";
|
||||
"lng_media_photo_empty" = "No photos here yet";
|
||||
"lng_media_gif_empty" = "No GIFs here yet";
|
||||
"lng_media_video_empty" = "No videos here yet";
|
||||
@@ -2495,11 +2520,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_topic_created_inside" = "Topic created";
|
||||
"lng_action_topic_closed_inside" = "Topic closed";
|
||||
"lng_action_topic_reopened_inside" = "Topic reopened";
|
||||
"lng_action_topic_closed_inside_by" = "{from} closed the topic";
|
||||
"lng_action_topic_reopened_inside_by" = "{from} reopened the topic";
|
||||
"lng_action_topic_hidden_inside" = "Topic hidden";
|
||||
"lng_action_topic_unhidden_inside" = "Topic unhidden";
|
||||
"lng_action_topic_created" = "The topic \"{topic}\" was created";
|
||||
"lng_action_topic_closed" = "\"{topic}\" was closed";
|
||||
"lng_action_topic_reopened" = "\"{topic}\" was reopened";
|
||||
"lng_action_topic_closed_by" = "{from} closed \"{topic}\"";
|
||||
"lng_action_topic_reopened_by" = "{from} reopened \"{topic}\"";
|
||||
"lng_action_topic_hidden" = "\"{topic}\" was hidden";
|
||||
"lng_action_topic_unhidden" = "\"{topic}\" was unhidden";
|
||||
"lng_action_topic_placeholder" = "topic";
|
||||
@@ -2555,6 +2584,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_todo_marked_not_done_self" = "You marked {tasks} as not done.";
|
||||
"lng_action_todo_added" = "{from} added {tasks} to the list.";
|
||||
"lng_action_todo_added_self" = "You added {tasks} to the list.";
|
||||
"lng_action_poll_added_answer" = "{from} added \"{option}\" to the poll.";
|
||||
"lng_action_poll_added_answer_self" = "You added \"{option}\" to the poll.";
|
||||
"lng_action_poll_deleted_answer" = "{from} removed \"{option}\" from the poll.";
|
||||
"lng_action_poll_deleted_answer_self" = "You removed \"{option}\" from the poll.";
|
||||
"lng_action_todo_tasks_fallback#one" = "task";
|
||||
"lng_action_todo_tasks_fallback#other" = "{count} tasks";
|
||||
"lng_action_todo_tasks_and_one" = "{tasks}, {task}";
|
||||
@@ -2577,6 +2610,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_stake_game_lost_you" = "You lost {amount}";
|
||||
"lng_action_change_creator" = "{from} made {user} the new main admin of the group.";
|
||||
"lng_action_new_creator_pending" = "{user} will become the new main admin in 7 days if {from} does not return.";
|
||||
"lng_action_managed_bot_created" = "{from} created a bot {bot}.";
|
||||
|
||||
"lng_create_bot_title" = "Create Bot";
|
||||
"lng_create_bot_subtitle" = "{bot} would like to create and manage a chatbot on your behalf.";
|
||||
"lng_create_bot_name_placeholder" = "Bot Name";
|
||||
"lng_create_bot_username_placeholder" = "Bot Username";
|
||||
"lng_create_bot_username_available" = "{username} is available.";
|
||||
"lng_create_bot_username_link" = "Link: {link}";
|
||||
"lng_create_bot_username_taken" = "This username is already taken.";
|
||||
"lng_create_bot_username_bad_symbols" = "Username can only contain a-z, 0-9, and underscores.";
|
||||
"lng_create_bot_username_too_short" = "Username must be at least 5 characters.";
|
||||
"lng_create_bot_button" = "Create";
|
||||
"lng_managed_bot_label" = "{icon} Created and managed by {bot}.";
|
||||
"lng_managed_bot_ready" = "**{name}** is ready!\n\nClick **Start** below to test your new chatbot. Its behavior is defined by **{parent}**.";
|
||||
"lng_managed_bot_created_title" = "{name} created!";
|
||||
"lng_managed_bot_created_text" = "{parent_name} will manage this bot for you.";
|
||||
"lng_managed_bot_edit_photo" = "You can edit your bot's profile picture {link}";
|
||||
"lng_managed_bot_edit_photo_link" = "here {arrow}";
|
||||
"lng_managed_bot_set_photo" = "Set Profile Photo";
|
||||
|
||||
"lng_create_bot_no_manage" = "{bot} doesn't have Bot Management Mode enabled.";
|
||||
|
||||
"lng_bots_create_limit#one" = "Subscribe to {link} to create up to {premium_count} bots, or delete one of your **{count}** bot via {bot}.";
|
||||
"lng_bots_create_limit#other" = "Subscribe to {link} to create up to {premium_count} bots, or delete one of your **{count}** bots via {bot}.";
|
||||
"lng_bots_create_limit_link" = "Premium";
|
||||
"lng_bots_create_limit_final#one" = "You can create up to **{count}** bot. Delete your current ones via {bot}.";
|
||||
"lng_bots_create_limit_final#other" = "You can create up to **{count}** bots. Delete your current ones via {bot}.";
|
||||
|
||||
"lng_stake_game_title" = "Emoji Stake";
|
||||
"lng_stake_game_beta" = "Beta";
|
||||
@@ -2980,6 +3040,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_about_gifts" = "Gifts are collectible items you can trade or showcase on your profile.";
|
||||
"lng_premium_summary_subtitle_no_forwards" = "Disable Sharing";
|
||||
"lng_premium_summary_about_no_forwards" = "Restrict forwarding, copying, and saving content from your private chats.";
|
||||
"lng_premium_summary_subtitle_ai_compose" = "AI Tools";
|
||||
"lng_premium_summary_about_ai_compose" = "Transform your messages and entire chats in your preferred style and language.";
|
||||
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
|
||||
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
@@ -4165,9 +4227,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_resale_date" = "Date";
|
||||
"lng_gift_resale_count#one" = "{count} gift in resale";
|
||||
"lng_gift_resale_count#other" = "{count} gifts in resale";
|
||||
"lng_gift_resale_count_none" = "No listings";
|
||||
"lng_gift_resale_sort_price" = "Sort by Price";
|
||||
"lng_gift_resale_sort_date" = "Sort by Date";
|
||||
"lng_gift_resale_sort_number" = "Sort by Number";
|
||||
"lng_gift_resale_all_listings" = "All Listings";
|
||||
"lng_gift_resale_stars_only" = "For Stars Only";
|
||||
"lng_gift_resale_filter_all" = "Select All";
|
||||
"lng_gift_resale_model" = "Model";
|
||||
"lng_gift_resale_models#one" = "{count} Model";
|
||||
@@ -4180,6 +4245,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_resale_symbols#other" = "{count} Symbols";
|
||||
"lng_gift_resale_switch_to_ton" = "Switch to Ton";
|
||||
"lng_gift_resale_switch_to_stars" = "Switch to Stars";
|
||||
"lng_gift_resale_search_none" = "No gifts found for your search.";
|
||||
"lng_gift_resale_early" = "You will be able to resell this gift in {duration}.";
|
||||
"lng_gift_transfer_early" = "You will be able to transfer this gift in {duration}.";
|
||||
"lng_gift_resale_transfer_early_title" = "Try Later";
|
||||
@@ -4759,6 +4825,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_url_auth_login_title" = "Log in to {domain}";
|
||||
"lng_url_auth_login_button" = "Log in";
|
||||
"lng_url_auth_site_access" = "This site will receive your **name**, **username** and **profile photo**.";
|
||||
"lng_url_auth_app_access" = "This app will receive your **name**, **username** and **profile photo**.";
|
||||
"lng_url_auth_unverified_app" = "Unverified App";
|
||||
"lng_url_auth_device_label" = "Device";
|
||||
"lng_url_auth_ip_label" = "IP Address";
|
||||
"lng_url_auth_login_attempt" = "This login attempt came from the device above.";
|
||||
@@ -4925,6 +4993,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_mark_read_all_sure_2" = "**This action cannot be undone.**";
|
||||
"lng_context_mark_read_mentions_all" = "Mark all mentions as read";
|
||||
"lng_context_mark_read_reactions_all" = "Read all reactions";
|
||||
"lng_context_mark_read_poll_votes_all" = "Read all poll votes";
|
||||
"lng_context_archive_expand" = "Expand";
|
||||
"lng_context_archive_collapse" = "Collapse";
|
||||
"lng_context_archive_to_menu" = "Move to main menu";
|
||||
@@ -4981,8 +5050,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_to_msg" = "Go To Message";
|
||||
"lng_context_reply_msg" = "Reply";
|
||||
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||
"lng_context_reply_with_timecode" = "Reply with timecode";
|
||||
"lng_context_reply_to_task" = "Reply to Task";
|
||||
"lng_context_reply_to_poll_option" = "Reply to Option";
|
||||
"lng_context_copy_poll_option" = "Copy Option";
|
||||
"lng_context_copy_poll_option_link" = "Copy Option Link";
|
||||
"lng_context_delete_poll_option" = "Delete Item";
|
||||
"lng_context_poll_message_tab" = "Poll";
|
||||
"lng_context_poll_option_tab" = "Option";
|
||||
"lng_context_edit_msg" = "Edit";
|
||||
"lng_context_draw" = "Edit Image";
|
||||
"lng_context_add_factcheck" = "Add Fact Check";
|
||||
"lng_context_edit_factcheck" = "Edit Fact Check";
|
||||
"lng_context_add_offer" = "Add Offer";
|
||||
@@ -5084,7 +5161,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_noforwards_info_mine" = "You disabled copying and forwarding in this chat.";
|
||||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
"lng_context_edit_cover" = "Edit Cover";
|
||||
@@ -5201,6 +5277,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_grouped" = "Group items";
|
||||
"lng_send_compressed_one" = "Compress the image";
|
||||
"lng_send_compressed" = "Compress images";
|
||||
"lng_send_high_quality" = "High Quality";
|
||||
"lng_send_as_documents_one" = "Send as a document";
|
||||
"lng_send_as_documents" = "Send as documents";
|
||||
"lng_send_media_invalid_files" = "Sorry, no valid files found.";
|
||||
@@ -5225,6 +5302,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_preview_reply_to" = "Reply to {name}";
|
||||
"lng_preview_reply_to_quote" = "Reply to quote from {name}";
|
||||
"lng_preview_reply_to_task" = "Reply to task from {title}";
|
||||
"lng_preview_reply_to_poll_option" = "Reply to poll option from {title}";
|
||||
|
||||
"lng_suggest_bar_title" = "Suggest a Post Below";
|
||||
"lng_suggest_bar_text" = "Click to offer a price for publishing.";
|
||||
@@ -5827,6 +5905,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_screen_share_stop" = "Stop Sharing";
|
||||
"lng_group_call_screen_title" = "Screen {index}";
|
||||
"lng_group_call_screen_share_audio" = "Share System Audio";
|
||||
"lng_group_call_sharing_screen_options" = "Sharing Options";
|
||||
"lng_group_call_choose_source" = "Choose Source";
|
||||
"lng_group_call_unmute" = "Unmute";
|
||||
"lng_group_call_unmute_sub" = "Hold space bar to temporarily unmute.";
|
||||
"lng_group_call_you_are_live" = "You are Live";
|
||||
@@ -6813,9 +6893,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_language_not_ready_link" = "translation platform";
|
||||
|
||||
"lng_translate_box_error" = "Translate failed.";
|
||||
"lng_translate_box_error_language_pack_not_installed" = "Translation requires a local language pack. Download it in System Settings.";
|
||||
|
||||
"lng_translate_settings_subtitle" = "Translate Messages";
|
||||
"lng_translate_settings_show" = "Show Translate Button";
|
||||
"lng_translate_settings_use_platform_mac" = "Use Apple Translations";
|
||||
"lng_translate_settings_use_platform_mac_about" = "Translation on macOS won't work until you download local language packs in System Settings.";
|
||||
"lng_translate_settings_use_platform_linux" = "Use KDE's Crow Translate";
|
||||
"lng_translate_settings_chat" = "Translate Entire Chats";
|
||||
"lng_translate_settings_choose" = "Do Not Translate";
|
||||
"lng_translate_settings_about" = "The 'Translate' button will appear in the context menu of messages containing text.";
|
||||
@@ -6843,6 +6927,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_answers_none" = "No answers";
|
||||
"lng_polls_submit_votes" = "Vote";
|
||||
"lng_polls_view_results" = "View results";
|
||||
"lng_polls_view_votes#one" = "View Votes ({count})";
|
||||
"lng_polls_view_votes#other" = "View Votes ({count})";
|
||||
"lng_polls_admin_votes#one" = "{count} vote {arrow}";
|
||||
"lng_polls_admin_votes#other" = "{count} votes {arrow}";
|
||||
"lng_polls_admin_back_vote" = "{arrow} Vote";
|
||||
"lng_polls_ends_in_days#one" = "ends in {count} day";
|
||||
"lng_polls_ends_in_days#other" = "ends in {count} days";
|
||||
"lng_polls_results_in_days#one" = "results in {count} day";
|
||||
"lng_polls_results_in_days#other" = "results in {count} days";
|
||||
"lng_polls_ends_in_time" = "ends in {time}";
|
||||
"lng_polls_results_in_time" = "results in {time}";
|
||||
"lng_polls_results_after_close" = "Results will appear after the poll ends.";
|
||||
"lng_polls_retract" = "Retract vote";
|
||||
"lng_polls_stop" = "Stop poll";
|
||||
"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
|
||||
@@ -6852,12 +6948,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_create_title" = "New poll";
|
||||
"lng_polls_create_question" = "Question";
|
||||
"lng_polls_create_question_placeholder" = "Ask a question";
|
||||
"lng_polls_create_description_placeholder" = "Add Description (optional)";
|
||||
"lng_polls_create_options" = "Poll options";
|
||||
"lng_polls_create_option_add" = "Add an option...";
|
||||
"lng_polls_create_limit#one" = "You can add {count} more option.";
|
||||
"lng_polls_create_limit#other" = "You can add {count} more options.";
|
||||
"lng_polls_create_maximum" = "You have added the maximum number of options.";
|
||||
"lng_polls_create_settings" = "Settings";
|
||||
"lng_polls_create_show_who_voted" = "Show Who Voted";
|
||||
"lng_polls_create_show_who_voted_about" = "Display voter name on each option.";
|
||||
"lng_polls_create_allow_multiple_answers" = "Allow Multiple Answers";
|
||||
"lng_polls_create_allow_multiple_answers_about" = "Voters can select more than one option.";
|
||||
"lng_polls_create_allow_adding_options" = "Allow Adding Options";
|
||||
"lng_polls_create_allow_adding_options_about" = "Participants can suggest new options.";
|
||||
"lng_polls_create_allow_revoting" = "Allow Revoting";
|
||||
"lng_polls_create_allow_revoting_about" = "Voters can change their vote.";
|
||||
"lng_polls_create_shuffle_options" = "Shuffle Options";
|
||||
"lng_polls_create_shuffle_options_about" = "Answers appear in random order for each voter.";
|
||||
"lng_polls_create_set_correct_answer" = "Set Correct Answer";
|
||||
"lng_polls_create_set_correct_answer_about" = "Mark one option as the right answer.";
|
||||
"lng_polls_create_set_correct_answer_about_multi" = "Mark one or more options as the right answer.";
|
||||
"lng_polls_create_limit_duration" = "Limit Duration";
|
||||
"lng_polls_create_limit_duration_about" = "Automatically close the poll at a set time.";
|
||||
"lng_polls_create_poll_duration" = "Poll Duration";
|
||||
"lng_polls_create_poll_ends" = "Poll ends";
|
||||
"lng_polls_create_hide_results" = "Hide results";
|
||||
"lng_polls_create_hide_results_about" = "If you switch this on, results will appear only after the poll closes.";
|
||||
"lng_polls_create_duration_custom" = "Custom";
|
||||
"lng_polls_create_deadline_title" = "Deadline";
|
||||
"lng_polls_create_deadline_button" = "Set Deadline";
|
||||
"lng_polls_create_deadline_expired" = "The poll deadline has already passed. Please choose a new time.";
|
||||
"lng_polls_create_anonymous" = "Anonymous Voting";
|
||||
"lng_polls_create_multiple_choice" = "Multiple Answers";
|
||||
"lng_polls_create_quiz_mode" = "Quiz Mode";
|
||||
@@ -6869,6 +6989,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_solution_title" = "Explanation";
|
||||
"lng_polls_solution_placeholder" = "Add a Comment (Optional)";
|
||||
"lng_polls_solution_about" = "Users will see this comment after choosing a wrong answer, good for educational purposes.";
|
||||
"lng_polls_media_uploading_toast_title" = "Please wait";
|
||||
"lng_polls_media_uploading_toast" = "Poll media is still uploading...";
|
||||
"lng_polls_ends_toast" = "Results will appear after the poll ends.";
|
||||
|
||||
"lng_polls_poll_results_title" = "Poll results";
|
||||
"lng_polls_quiz_results_title" = "Quiz results";
|
||||
@@ -6876,6 +6999,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_show_more#other" = "Show more ({count})";
|
||||
"lng_polls_votes_collapse" = "Collapse";
|
||||
"lng_polls_vote_yesterday" = "yesterday";
|
||||
"lng_polls_option_added_by" = "Added by {user}";
|
||||
"lng_polls_context_ends" = "Results will appear after the poll ends.";
|
||||
"lng_polls_add_option" = "Add an Option";
|
||||
"lng_polls_add_option_placeholder" = "Option text...";
|
||||
"lng_polls_max_options_reached" = "Maximum number of options reached.";
|
||||
"lng_polls_add_option_duplicate" = "This option already exists.";
|
||||
"lng_polls_add_option_closed" = "This poll has been closed.";
|
||||
"lng_polls_add_option_error" = "Could not add the option. Please try again.";
|
||||
"lng_polls_add_option_save" = "Save";
|
||||
|
||||
"lng_todo_title" = "Checklist";
|
||||
"lng_todo_title_group" = "Group Checklist";
|
||||
@@ -6912,6 +7044,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_outdated_soon" = "Otherwise, Telegram Desktop will stop updating on {date}.";
|
||||
"lng_outdated_now" = "So Telegram Desktop can update to newer versions.";
|
||||
|
||||
"lng_screen_reader_bar_text" = "Telegram is working in Screen Reader mode.";
|
||||
"lng_screen_reader_bar_disable" = "Disable";
|
||||
"lng_screen_reader_confirm_text" = "Telegram detected accessibility software is being used in your system and it is working in Screen Reader friendly mode.\n\nThis may result in unexpected changes to the way Telegram user interface works.\n\nIf you do not use Screen Reader software with Telegram, please disable this mode.";
|
||||
"lng_screen_reader_confirm_disable" = "Disable";
|
||||
"lng_screen_reader_settings_title" = "Screen reader";
|
||||
"lng_screen_reader_settings_disable" = "Disable screen reader mode";
|
||||
|
||||
"lng_filters_all" = "All chats";
|
||||
"lng_filters_all_short" = "All";
|
||||
"lng_filters_setup" = "Edit";
|
||||
@@ -7066,6 +7205,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_photo_editor_menu_flip" = "Flip";
|
||||
"lng_photo_editor_menu_duplicate" = "Duplicate";
|
||||
|
||||
"lng_photo_editor_crop_original" = "Original";
|
||||
"lng_photo_editor_crop_square" = "Square";
|
||||
"lng_photo_editor_crop_free" = "Free";
|
||||
|
||||
"lng_voice_speed_slow" = "Slow";
|
||||
"lng_voice_speed_normal" = "Normal";
|
||||
"lng_voice_speed_medium" = "Medium";
|
||||
@@ -7095,6 +7238,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_view_button_call" = "Join call";
|
||||
"lng_view_button_storyalbum" = "View Album";
|
||||
"lng_view_button_collection" = "View Collection";
|
||||
"lng_view_button_newbot" = "Create Bot";
|
||||
|
||||
"lng_sponsored_hide_ads" = "Hide";
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
@@ -7694,6 +7838,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_new_window_tooltip_ctrl" = "Use Ctrl+Click to open in New Window.";
|
||||
"lng_new_window_tooltip_cmd" = "Use Cmd+Click to open in New Window.";
|
||||
|
||||
"lng_rename_file" = "Rename file";
|
||||
|
||||
"lng_sr_playback_order" = "Playback order";
|
||||
"lng_sr_player_close" = "Close media player";
|
||||
"lng_sr_group_call_menu" = "Video chat menu";
|
||||
"lng_sr_search_date" = "Search by date";
|
||||
"lng_sr_cancel_search" = "Cancel search";
|
||||
"lng_sr_clear_search" = "Clear search";
|
||||
"lng_sr_scroll_to_top" = "Scroll to top";
|
||||
"lng_sr_verified_badge" = "Verified";
|
||||
"lng_sr_bot_verified_badge" = "Verified Bot";
|
||||
"lng_sr_profile_menu" = "Profile menu";
|
||||
"lng_sr_close_panel" = "Close panel";
|
||||
|
||||
"lng_ai_compose_title" = "AI Editor";
|
||||
"lng_ai_compose_apply" = "Apply";
|
||||
"lng_ai_compose_tab_translate" = "Translate";
|
||||
"lng_ai_compose_tab_style" = "Style";
|
||||
"lng_ai_compose_tab_fix" = "Fix";
|
||||
"lng_ai_compose_original" = "Original";
|
||||
"lng_ai_compose_result" = "Result";
|
||||
"lng_ai_compose_to_language" = "To {language}";
|
||||
"lng_ai_compose_name_style" = "{name} ({style})";
|
||||
"lng_ai_compose_style_neutral" = "Neutral";
|
||||
"lng_ai_compose_emojify" = "emojify";
|
||||
"lng_ai_compose_error" = "AI request failed.";
|
||||
"lng_ai_compose_tooltip" = "Rewrite, translate, or correct your text using AI.";
|
||||
"lng_ai_compose_flood_title" = "Daily limit reached";
|
||||
"lng_ai_compose_flood_text" = "Get {link} for **50x** more AI text transformations per day.";
|
||||
"lng_ai_compose_flood_link" = "Telegram Premium";
|
||||
"lng_ai_compose_increase_limit" = "Increase Limit";
|
||||
"lng_ai_compose_select_style" = "Select Style";
|
||||
"lng_ai_compose_apply_style" = "Apply Style";
|
||||
"lng_ai_compose_style_tooltip" = "Choose Style";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
@@ -7760,18 +7939,4 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_mac_hold_to_quit" = "Hold {text} to Quit";
|
||||
|
||||
"lng_rename_file" = "Rename file";
|
||||
|
||||
"lng_sr_playback_order" = "Playback order";
|
||||
"lng_sr_player_close" = "Close media player";
|
||||
"lng_sr_group_call_menu" = "Video chat menu";
|
||||
"lng_sr_search_date" = "Search by date";
|
||||
"lng_sr_cancel_search" = "Cancel search";
|
||||
"lng_sr_clear_search" = "Clear search";
|
||||
"lng_sr_scroll_to_top" = "Scroll to top";
|
||||
"lng_sr_verified_badge" = "Verified";
|
||||
"lng_sr_bot_verified_badge" = "Verified Bot";
|
||||
"lng_sr_profile_menu" = "Profile menu";
|
||||
"lng_sr_close_panel" = "Close panel";
|
||||
|
||||
// Keys finished
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
|
||||
<file alias="chat/voice_to_video.tgs">../../animations/chat/voice_to_video.tgs</file>
|
||||
<file alias="chat/video_to_voice.tgs">../../animations/chat/video_to_voice.tgs</file>
|
||||
<file alias="chat/sparkles_emoji.tgs">../../animations/chat/sparkles_emoji.tgs</file>
|
||||
<file alias="chat/white_flag_emoji.tgs">../../animations/chat/white_flag_emoji.tgs</file>
|
||||
<file alias="palette.tgs">../../animations/palette.tgs</file>
|
||||
<file alias="sleep.tgs">../../animations/sleep.tgs</file>
|
||||
<file alias="greeting.tgs">../../animations/greeting.tgs</file>
|
||||
@@ -57,6 +59,8 @@
|
||||
<file alias="cocoon.tgs">../../animations/cocoon.tgs</file>
|
||||
<file alias="craft_failed.tgs">../../animations/craft_failed.tgs</file>
|
||||
<file alias="stop.tgs">../../animations/stop.tgs</file>
|
||||
<file alias="toast_hide_results.tgs">../../icons/poll/toast_hide_results.tgs</file>
|
||||
<file alias="uploading.tgs">../../icons/poll/uploading.tgs</file>
|
||||
|
||||
<file alias="profile_muting.tgs">../../animations/profile/profile_muting.tgs</file>
|
||||
<file alias="profile_unmuting.tgs">../../animations/profile/profile_unmuting.tgs</file>
|
||||
@@ -81,6 +85,11 @@
|
||||
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
|
||||
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
|
||||
|
||||
<file alias="photo_editor_pen.tgs">../../animations/photo_editor/pen.tgs</file>
|
||||
<file alias="photo_editor_arrow.tgs">../../animations/photo_editor/arrow.tgs</file>
|
||||
<file alias="photo_editor_marker.tgs">../../animations/photo_editor/marker.tgs</file>
|
||||
<file alias="photo_editor_eraser.tgs">../../animations/photo_editor/eraser.tgs</file>
|
||||
|
||||
<file alias="swipe_archive.tgs">../../animations/swipe_action/archive.tgs</file>
|
||||
<file alias="swipe_unarchive.tgs">../../animations/swipe_action/unarchive.tgs</file>
|
||||
<file alias="swipe_delete.tgs">../../animations/swipe_action/delete.tgs</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="6.6.2.0" />
|
||||
Version="6.7.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,6,2,0
|
||||
PRODUCTVERSION 6,6,2,0
|
||||
FILEVERSION 6,7,0,0
|
||||
PRODUCTVERSION 6,7,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", ""
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "6.6.2.0"
|
||||
VALUE "FileVersion", "6.7.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2026"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.6.2.0"
|
||||
VALUE "ProductVersion", "6.7.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,6,2,0
|
||||
PRODUCTVERSION 6,6,2,0
|
||||
FILEVERSION 6,7,0,0
|
||||
PRODUCTVERSION 6,7,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", ""
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "6.6.2.0"
|
||||
VALUE "FileVersion", "6.7.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2026"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "6.6.2.0"
|
||||
VALUE "ProductVersion", "6.7.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -71,7 +71,8 @@ MTPinputMedia InputMediaFromItem(not_null<HistoryItem*> i) {
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(MTPDinputMediaPhoto::Flag(0)),
|
||||
photo->mtpInput(),
|
||||
MTP_int(0));
|
||||
MTP_int(0),
|
||||
MTPInputDocument());
|
||||
} else {
|
||||
return MTP_inputMediaEmpty();
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "api/api_send_progress.h"
|
||||
#include "api/api_suggest_post.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "boxes/url_auth_box.h"
|
||||
#include "boxes/peers/choose_peer_box.h"
|
||||
#include "boxes/peers/create_managed_bot_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/url_auth_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "chat_helpers/bot_command.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
@@ -39,7 +40,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
@@ -391,7 +394,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
|
||||
case ButtonType::RequestPoll: {
|
||||
HideSingleUseKeyboard(controller, item);
|
||||
auto chosen = PollData::Flags();
|
||||
auto chosen = kDefaultPollCreateFlags;
|
||||
auto disabled = PollData::Flags();
|
||||
if (!button->data.isEmpty()) {
|
||||
disabled |= PollData::Flag::Quiz;
|
||||
@@ -420,9 +423,12 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
const auto itemId = item->id;
|
||||
const auto id = int32(button->buttonId);
|
||||
const auto chosen = [=](std::vector<not_null<PeerData*>> result) {
|
||||
using Flag = MTPmessages_SendBotRequestedPeer::Flag;
|
||||
peer->session().api().request(MTPmessages_SendBotRequestedPeer(
|
||||
MTP_flags(Flag::f_msg_id),
|
||||
peer->input(),
|
||||
MTP_int(itemId),
|
||||
MTPstring(), // request_id
|
||||
MTP_int(id),
|
||||
MTP_vector_from_range(
|
||||
result | ranges::views::transform([](
|
||||
@@ -543,6 +549,58 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
QVariant::fromValue(context),
|
||||
});
|
||||
} break;
|
||||
|
||||
case ButtonType::CreateBot: {
|
||||
HideSingleUseKeyboard(controller, item);
|
||||
|
||||
auto suggestedName = QString();
|
||||
auto suggestedUsername = QString();
|
||||
{
|
||||
auto stream = QDataStream(button->data);
|
||||
stream >> suggestedName >> suggestedUsername;
|
||||
}
|
||||
const auto peer = item->history()->peer;
|
||||
const auto itemId = item->id;
|
||||
const auto id = int32(button->buttonId);
|
||||
const auto bot = item->getMessageBot();
|
||||
if (!bot) {
|
||||
break;
|
||||
}
|
||||
ShowCreateManagedBotBox({
|
||||
.show = controller->uiShow(),
|
||||
.manager = bot,
|
||||
.suggestedName = suggestedName,
|
||||
.suggestedUsername = suggestedUsername,
|
||||
.done = [=](not_null<UserData*> createdBot) {
|
||||
using Flag = MTPmessages_SendBotRequestedPeer::Flag;
|
||||
peer->session().api().request(
|
||||
MTPmessages_SendBotRequestedPeer(
|
||||
MTP_flags(Flag::f_msg_id),
|
||||
peer->input(),
|
||||
MTP_int(itemId),
|
||||
MTPstring(),
|
||||
MTP_int(id),
|
||||
MTP_vector<MTPInputPeer>(
|
||||
1,
|
||||
createdBot->input()))
|
||||
).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
}).send();
|
||||
controller->showPeerHistory(createdBot);
|
||||
controller->showToast({
|
||||
.title = tr::lng_managed_bot_created_title(
|
||||
tr::now,
|
||||
lt_name,
|
||||
createdBot->name()),
|
||||
.text = { tr::lng_managed_bot_created_text(
|
||||
tr::now,
|
||||
lt_parent_name,
|
||||
bot->name()) },
|
||||
.icon = &st::toastCheckIcon,
|
||||
});
|
||||
},
|
||||
});
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,9 +151,8 @@ void ApplyLastList(
|
||||
}
|
||||
if (user->isBot()) {
|
||||
channel->mgInfo->bots.insert(user);
|
||||
if ((channel->mgInfo->botStatus != 0)
|
||||
&& (channel->mgInfo->botStatus < 2)) {
|
||||
channel->mgInfo->botStatus = 2;
|
||||
if (channel->mgInfo->botStatus == Data::BotStatus::NoBots) {
|
||||
channel->mgInfo->botStatus = Data::BotStatus::HasBots;
|
||||
}
|
||||
}
|
||||
if (!p.rank().isEmpty()) {
|
||||
@@ -189,7 +188,7 @@ void ApplyBotsList(
|
||||
Members list) {
|
||||
const auto history = channel->owner().historyLoaded(channel);
|
||||
channel->mgInfo->bots.clear();
|
||||
channel->mgInfo->botStatus = -1;
|
||||
channel->mgInfo->botStatus = Data::BotStatus::NoBots;
|
||||
|
||||
auto needBotsInfos = false;
|
||||
auto botStatus = channel->mgInfo->botStatus;
|
||||
@@ -199,7 +198,7 @@ void ApplyBotsList(
|
||||
const auto user = participant->asUser();
|
||||
if (user && user->isBot()) {
|
||||
channel->mgInfo->bots.insert(user);
|
||||
botStatus = 2;// (botStatus > 0/* || !i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
|
||||
botStatus = Data::BotStatus::HasBots;
|
||||
if (!user->botInfo->inited) {
|
||||
needBotsInfos = true;
|
||||
}
|
||||
@@ -516,7 +515,7 @@ void ChatParticipants::requestBots(not_null<ChannelData*> channel) {
|
||||
_botsRequests.remove(channel);
|
||||
if (error.type() == u"CHANNEL_MONOFORUM_UNSUPPORTED"_q) {
|
||||
channel->mgInfo->bots.clear();
|
||||
channel->mgInfo->botStatus = -1;
|
||||
channel->mgInfo->botStatus = Data::BotStatus::NoBots;
|
||||
channel->session().changes().peerUpdated(
|
||||
channel,
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_compose_with_ai.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] MTPTextWithEntities Serialize(
|
||||
not_null<Main::Session*> session,
|
||||
const TextWithEntities &text) {
|
||||
return MTP_textWithEntities(
|
||||
MTP_string(text.text),
|
||||
EntitiesToMTP(session, text.entities, ConvertOption::SkipLocal));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ComposeWithAi::ComposeWithAi(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
}
|
||||
|
||||
mtpRequestId ComposeWithAi::request(
|
||||
Request request,
|
||||
Fn<void(Result &&)> done,
|
||||
Fn<void(const MTP::Error &)> fail) {
|
||||
using Flag = MTPmessages_composeMessageWithAI::Flag;
|
||||
auto flags = MTPmessages_composeMessageWithAI::Flags(0);
|
||||
if (request.proofread) {
|
||||
flags |= Flag::f_proofread;
|
||||
}
|
||||
if (!request.translateToLang.isEmpty()) {
|
||||
flags |= Flag::f_translate_to_lang;
|
||||
}
|
||||
if (!request.changeTone.isEmpty()) {
|
||||
flags |= Flag::f_change_tone;
|
||||
}
|
||||
if (request.emojify) {
|
||||
flags |= Flag::f_emojify;
|
||||
}
|
||||
const auto session = _session;
|
||||
return _api.request(MTPmessages_ComposeMessageWithAI(
|
||||
MTP_flags(flags),
|
||||
Serialize(session, request.text),
|
||||
request.translateToLang.isEmpty()
|
||||
? MTPstring()
|
||||
: MTP_string(request.translateToLang),
|
||||
request.changeTone.isEmpty()
|
||||
? MTPstring()
|
||||
: MTP_string(request.changeTone)
|
||||
)).done([=, done = std::move(done)](
|
||||
const MTPmessages_ComposedMessageWithAI &result) mutable {
|
||||
const auto &data = result.data();
|
||||
auto parsed = Result{
|
||||
.resultText = ParseTextWithEntities(session, data.vresult_text()),
|
||||
};
|
||||
if (const auto diff = data.vdiff_text()) {
|
||||
parsed.diffText = ParseDiff(session, *diff);
|
||||
}
|
||||
done(std::move(parsed));
|
||||
}).fail([=, fail = std::move(fail)](const MTP::Error &error) mutable {
|
||||
if (fail) {
|
||||
fail(error);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ComposeWithAi::cancel(mtpRequestId requestId) {
|
||||
if (requestId) {
|
||||
_api.request(requestId).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
ComposeWithAi::Diff ComposeWithAi::ParseDiff(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPTextWithEntities &text) {
|
||||
const auto &data = text.data();
|
||||
auto result = Diff{
|
||||
.text = ParseTextWithEntities(session, text),
|
||||
};
|
||||
const auto &entities = data.ventities().v;
|
||||
result.entities.reserve(entities.size());
|
||||
for (const auto &entity : entities) {
|
||||
entity.match([&](const MTPDmessageEntityDiffInsert &data) {
|
||||
result.entities.push_back({
|
||||
.type = DiffEntity::Type::Insert,
|
||||
.offset = data.voffset().v,
|
||||
.length = data.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityDiffReplace &data) {
|
||||
result.entities.push_back({
|
||||
.type = DiffEntity::Type::Replace,
|
||||
.offset = data.voffset().v,
|
||||
.length = data.vlength().v,
|
||||
.oldText = qs(data.vold_text()),
|
||||
});
|
||||
}, [&](const MTPDmessageEntityDiffDelete &data) {
|
||||
result.entities.push_back({
|
||||
.type = DiffEntity::Type::Delete,
|
||||
.offset = data.voffset().v,
|
||||
.length = data.vlength().v,
|
||||
});
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class ApiWrap;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
class ComposeWithAi final {
|
||||
public:
|
||||
struct Request {
|
||||
TextWithEntities text;
|
||||
QString translateToLang;
|
||||
QString changeTone;
|
||||
bool proofread = false;
|
||||
bool emojify = false;
|
||||
};
|
||||
|
||||
struct DiffEntity {
|
||||
enum class Type {
|
||||
Insert,
|
||||
Replace,
|
||||
Delete,
|
||||
};
|
||||
|
||||
Type type = Type::Insert;
|
||||
int offset = 0;
|
||||
int length = 0;
|
||||
QString oldText;
|
||||
};
|
||||
|
||||
struct Diff {
|
||||
TextWithEntities text;
|
||||
std::vector<DiffEntity> entities;
|
||||
};
|
||||
|
||||
struct Result {
|
||||
TextWithEntities resultText;
|
||||
std::optional<Diff> diffText;
|
||||
};
|
||||
|
||||
explicit ComposeWithAi(not_null<ApiWrap*> api);
|
||||
|
||||
[[nodiscard]] mtpRequestId request(
|
||||
Request request,
|
||||
Fn<void(Result &&)> done,
|
||||
Fn<void(const MTP::Error &)> fail = nullptr);
|
||||
void cancel(mtpRequestId requestId);
|
||||
|
||||
private:
|
||||
[[nodiscard]] static Diff ParseDiff(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPTextWithEntities &text);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -207,7 +207,8 @@ mtpRequestId SuggestMessageOrMedia(
|
||||
inputMedia = MTP_inputMediaPhoto(
|
||||
MTP_flags(0),
|
||||
photo->mtpInput(),
|
||||
MTPint()); // ttl_seconds
|
||||
MTPint(), // ttl_seconds
|
||||
MTPInputDocument()); // video
|
||||
} else if (const auto document = wasMedia->document()) {
|
||||
inputMedia = MTP_inputMediaDocument(
|
||||
MTP_flags(0),
|
||||
@@ -479,7 +480,8 @@ mtpRequestId EditTextMessage(
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(flags),
|
||||
photo->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()));
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTPInputDocument()); // video
|
||||
};
|
||||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
|
||||
@@ -93,7 +93,8 @@ MTPInputMedia PrepareUploadedPhoto(
|
||||
info.file,
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTP_int(ttlSeconds));
|
||||
MTP_int(ttlSeconds),
|
||||
MTPInputDocument()); // video
|
||||
}
|
||||
|
||||
MTPInputMedia PrepareUploadedDocument(
|
||||
|
||||
@@ -66,6 +66,8 @@ MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
|
||||
|
||||
void MessagesSearchMerged::disableMigrated() {
|
||||
_migratedSearch = std::nullopt;
|
||||
_waitingForTotal = false;
|
||||
_isFull = false;
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::addFound(const FoundMessages &data) {
|
||||
@@ -85,12 +87,15 @@ const MessagesSearch::Request &MessagesSearchMerged::request() const {
|
||||
void MessagesSearchMerged::clear() {
|
||||
_concatedFound = {};
|
||||
_migratedFirstFound = {};
|
||||
_waitingForTotal = false;
|
||||
_isFull = false;
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::search(const Request &search) {
|
||||
_request = search;
|
||||
_isFull = false;
|
||||
_waitingForTotal = (_migratedSearch != std::nullopt);
|
||||
if (_migratedSearch) {
|
||||
_waitingForTotal = true;
|
||||
_migratedSearch->searchMessages(search);
|
||||
}
|
||||
_apiSearch.searchMessages(search);
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_polls.h"
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
@@ -30,9 +31,10 @@ Polls::Polls(not_null<ApiWrap*> api)
|
||||
|
||||
void Polls::create(
|
||||
const PollData &data,
|
||||
const TextWithEntities &text,
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail) {
|
||||
Fn<void(bool fileReferenceExpired)> fail) {
|
||||
_session->api().sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
@@ -82,6 +84,13 @@ void Polls::create(
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
text.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
if (!sentEntities.v.isEmpty()) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||
}
|
||||
auto &histories = history->owner().histories();
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
histories.sendPreparedMessage(
|
||||
@@ -93,10 +102,10 @@ void Polls::create(
|
||||
peer->input(),
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
PollDataToInputMedia(&data),
|
||||
MTP_string(),
|
||||
MTP_string(text.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
MTP_int(action.options.scheduleRepeatPeriod),
|
||||
(sendAs ? sendAs->input() : MTP_inputPeerEmpty()),
|
||||
@@ -124,7 +133,9 @@ void Polls::create(
|
||||
monoforumPeerId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
}
|
||||
fail();
|
||||
const auto expired = (error.code() == 400)
|
||||
&& error.type().startsWith(u"FILE_REFERENCE_"_q);
|
||||
fail(expired);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -176,6 +187,73 @@ void Polls::sendVotes(
|
||||
_pollVotesRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::addAnswer(
|
||||
FullMsgId itemId,
|
||||
const TextWithEntities &text,
|
||||
const PollMedia &media,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail) {
|
||||
if (_pollAddAnswerRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto item = _session->data().message(itemId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
text.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
using Flag = MTPDinputPollAnswer::Flag;
|
||||
const auto flags = media
|
||||
? Flag::f_media
|
||||
: Flag();
|
||||
const auto answer = MTP_inputPollAnswer(
|
||||
MTP_flags(flags),
|
||||
MTP_textWithEntities(
|
||||
MTP_string(text.text),
|
||||
sentEntities),
|
||||
media ? PollMediaToMTP(media) : MTPInputMedia());
|
||||
const auto requestId = _api.request(MTPmessages_AddPollAnswer(
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id),
|
||||
answer
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollAddAnswerRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollAddAnswerRequestIds.erase(itemId);
|
||||
if (fail) {
|
||||
fail(error.type());
|
||||
}
|
||||
}).send();
|
||||
_pollAddAnswerRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::deleteAnswer(FullMsgId itemId, const QByteArray &option) {
|
||||
if (_pollVotesRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto item = _session->data().message(itemId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto requestId = _api.request(MTPmessages_DeletePollAnswer(
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id),
|
||||
MTP_bytes(option)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
}).fail([=] {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
}).send();
|
||||
_pollVotesRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::close(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (_pollCloseRequestIds.contains(itemId)) {
|
||||
@@ -211,9 +289,13 @@ void Polls::reloadResults(not_null<HistoryItem*> item) {
|
||||
if (!item->isRegular() || _pollReloadRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto media = item->media();
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
const auto pollHash = poll ? poll->hash : uint64(0);
|
||||
const auto requestId = _api.request(MTPmessages_GetPollResults(
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id)
|
||||
MTP_int(item->id),
|
||||
MTP_long(pollHash)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollReloadRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
|
||||
@@ -8,10 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
|
||||
class ApiWrap;
|
||||
class HistoryItem;
|
||||
struct PollData;
|
||||
struct PollMedia;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
@@ -27,12 +29,20 @@ public:
|
||||
|
||||
void create(
|
||||
const PollData &data,
|
||||
const TextWithEntities &text,
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail);
|
||||
Fn<void(bool fileReferenceExpired)> fail);
|
||||
void sendVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options);
|
||||
void addAnswer(
|
||||
FullMsgId itemId,
|
||||
const TextWithEntities &text,
|
||||
const PollMedia &media,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
void deleteAnswer(FullMsgId itemId, const QByteArray &option);
|
||||
void close(not_null<HistoryItem*> item);
|
||||
void reloadResults(not_null<HistoryItem*> item);
|
||||
|
||||
@@ -41,6 +51,7 @@ private:
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollAddAnswerRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollReloadRequestIds;
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ namespace {
|
||||
+ QChar(0x00D7)
|
||||
+ ' '
|
||||
+ QString::number(tlOption.vusers().v);
|
||||
options[i].total = Ui::FillAmountAndCurrency(
|
||||
tlOption.vamount().v,
|
||||
currency);
|
||||
options[i].currency = currency;
|
||||
}
|
||||
return options;
|
||||
|
||||
@@ -15,29 +15,33 @@ constexpr auto kDiscountDivider = 1.;
|
||||
|
||||
Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
int monthlyAmount,
|
||||
float64 monthlyAmount,
|
||||
int64 amount,
|
||||
const QString ¤cy,
|
||||
const QString &botUrl) {
|
||||
const auto baselineAmount = monthlyAmount * months;
|
||||
const auto discount = [&] {
|
||||
const auto percent = 1. - float64(amount) / (monthlyAmount * months);
|
||||
return std::round(percent * 100. / kDiscountDivider)
|
||||
const auto percent = 1. - float64(amount) / baselineAmount;
|
||||
return base::SafeRound(percent * 100. / kDiscountDivider)
|
||||
* kDiscountDivider;
|
||||
}();
|
||||
const auto hasDiscount = (discount > 0);
|
||||
return {
|
||||
.months = months,
|
||||
.duration = Ui::FormatTTL(months * 86400 * 31),
|
||||
.discount = (discount > 0)
|
||||
.discount = hasDiscount
|
||||
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
||||
: QString(),
|
||||
.costPerMonth = Ui::FillAmountAndCurrency(
|
||||
amount / float64(months),
|
||||
currency),
|
||||
.costNoDiscount = Ui::FillAmountAndCurrency(
|
||||
monthlyAmount * months,
|
||||
int64(base::SafeRound(amount / float64(months))),
|
||||
currency),
|
||||
.costNoDiscount = hasDiscount
|
||||
? Ui::FillAmountAndCurrency(
|
||||
int64(base::SafeRound(baselineAmount)),
|
||||
currency)
|
||||
: QString(),
|
||||
.costPerYear = Ui::FillAmountAndCurrency(
|
||||
amount / float64(months / 12.),
|
||||
int64(base::SafeRound(amount / float64(months / 12.))),
|
||||
currency),
|
||||
.botUrl = botUrl,
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Api {
|
||||
|
||||
[[nodiscard]] Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
int monthlyAmount,
|
||||
float64 monthlyAmount,
|
||||
int64 amount,
|
||||
const QString ¤cy,
|
||||
const QString &botUrl);
|
||||
@@ -24,9 +24,9 @@ template<typename Option>
|
||||
if (tlOpts.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto monthlyAmountPerCurrency = base::flat_map<QString, int>();
|
||||
auto monthlyAmountPerCurrency = base::flat_map<QString, float64>();
|
||||
auto result = Data::PremiumSubscriptionOptions();
|
||||
const auto monthlyAmount = [&](const QString ¤cy) -> int {
|
||||
const auto monthlyAmount = [&](const QString ¤cy) -> float64 {
|
||||
const auto it = monthlyAmountPerCurrency.find(currency);
|
||||
if (it != end(monthlyAmountPerCurrency)) {
|
||||
return it->second;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_reactions_notify_settings.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] ReactionsNotifyFrom ParseFrom(
|
||||
const MTPReactionNotificationsFrom &from) {
|
||||
return from.match([](const MTPDreactionNotificationsFromContacts &) {
|
||||
return ReactionsNotifyFrom::Contacts;
|
||||
}, [](const MTPDreactionNotificationsFromAll &) {
|
||||
return ReactionsNotifyFrom::All;
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] MTPReactionNotificationsFrom SerializeFrom(
|
||||
ReactionsNotifyFrom from) {
|
||||
switch (from) {
|
||||
case ReactionsNotifyFrom::Contacts:
|
||||
return MTP_reactionNotificationsFromContacts();
|
||||
case ReactionsNotifyFrom::All:
|
||||
return MTP_reactionNotificationsFromAll();
|
||||
}
|
||||
Unexpected("Value in SerializeFrom.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReactionsNotifySettings::ReactionsNotifySettings(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::reload(Fn<void()> callback) {
|
||||
if (callback) {
|
||||
_callbacks.push_back(std::move(callback));
|
||||
}
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPaccount_GetReactionsNotifySettings(
|
||||
)).done([=](const MTPReactionsNotifySettings &result) {
|
||||
_requestId = 0;
|
||||
apply(result);
|
||||
for (const auto &callback : base::take(_callbacks)) {
|
||||
callback();
|
||||
}
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
for (const auto &callback : base::take(_callbacks)) {
|
||||
callback();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::updateMessagesFrom(ReactionsNotifyFrom value) {
|
||||
_messagesFrom = value;
|
||||
save();
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::updatePollVotesFrom(
|
||||
ReactionsNotifyFrom value) {
|
||||
_pollVotesFrom = value;
|
||||
save();
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::setAllFrom(ReactionsNotifyFrom value) {
|
||||
_messagesFrom = value;
|
||||
_pollVotesFrom = value;
|
||||
save();
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::updateShowPreviews(bool value) {
|
||||
_showPreviews = value;
|
||||
save();
|
||||
}
|
||||
|
||||
ReactionsNotifyFrom ReactionsNotifySettings::messagesFromCurrent() const {
|
||||
return _messagesFrom.current();
|
||||
}
|
||||
|
||||
rpl::producer<ReactionsNotifyFrom> ReactionsNotifySettings::messagesFrom() const {
|
||||
return _messagesFrom.value();
|
||||
}
|
||||
|
||||
ReactionsNotifyFrom ReactionsNotifySettings::pollVotesFromCurrent() const {
|
||||
return _pollVotesFrom.current();
|
||||
}
|
||||
|
||||
rpl::producer<ReactionsNotifyFrom> ReactionsNotifySettings::pollVotesFrom() const {
|
||||
return _pollVotesFrom.value();
|
||||
}
|
||||
|
||||
bool ReactionsNotifySettings::showPreviewsCurrent() const {
|
||||
return _showPreviews.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> ReactionsNotifySettings::showPreviews() const {
|
||||
return _showPreviews.value();
|
||||
}
|
||||
|
||||
bool ReactionsNotifySettings::enabledCurrent() const {
|
||||
return (_messagesFrom.current() != ReactionsNotifyFrom::None)
|
||||
|| (_pollVotesFrom.current() != ReactionsNotifyFrom::None);
|
||||
}
|
||||
|
||||
rpl::producer<bool> ReactionsNotifySettings::enabled() const {
|
||||
return rpl::combine(
|
||||
_messagesFrom.value(),
|
||||
_pollVotesFrom.value()
|
||||
) | rpl::map([](
|
||||
ReactionsNotifyFrom messages,
|
||||
ReactionsNotifyFrom pollVotes) {
|
||||
return (messages != ReactionsNotifyFrom::None)
|
||||
|| (pollVotes != ReactionsNotifyFrom::None);
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::apply(
|
||||
const MTPReactionsNotifySettings &settings) {
|
||||
const auto &data = settings.data();
|
||||
const auto messages = data.vmessages_notify_from();
|
||||
const auto stories = data.vstories_notify_from();
|
||||
const auto pollVotes = data.vpoll_votes_notify_from();
|
||||
_messagesFrom = messages
|
||||
? ParseFrom(*messages)
|
||||
: ReactionsNotifyFrom::None;
|
||||
_storiesFrom = stories
|
||||
? ParseFrom(*stories)
|
||||
: ReactionsNotifyFrom::None;
|
||||
_pollVotesFrom = pollVotes
|
||||
? ParseFrom(*pollVotes)
|
||||
: ReactionsNotifyFrom::None;
|
||||
_showPreviews = mtpIsTrue(data.vshow_previews());
|
||||
}
|
||||
|
||||
void ReactionsNotifySettings::save() {
|
||||
using Flag = MTPDreactionsNotifySettings::Flag;
|
||||
const auto messages = _messagesFrom.current();
|
||||
const auto stories = _storiesFrom.current();
|
||||
const auto pollVotes = _pollVotesFrom.current();
|
||||
const auto previews = _showPreviews.current();
|
||||
const auto flags = Flag()
|
||||
| ((messages != ReactionsNotifyFrom::None)
|
||||
? Flag::f_messages_notify_from
|
||||
: Flag())
|
||||
| ((stories != ReactionsNotifyFrom::None)
|
||||
? Flag::f_stories_notify_from
|
||||
: Flag())
|
||||
| ((pollVotes != ReactionsNotifyFrom::None)
|
||||
? Flag::f_poll_votes_notify_from
|
||||
: Flag());
|
||||
_api.request(base::take(_requestId)).cancel();
|
||||
_requestId = _api.request(MTPaccount_SetReactionsNotifySettings(
|
||||
MTP_reactionsNotifySettings(
|
||||
MTP_flags(flags),
|
||||
((messages != ReactionsNotifyFrom::None)
|
||||
? SerializeFrom(messages)
|
||||
: MTPReactionNotificationsFrom()),
|
||||
((stories != ReactionsNotifyFrom::None)
|
||||
? SerializeFrom(stories)
|
||||
: MTPReactionNotificationsFrom()),
|
||||
((pollVotes != ReactionsNotifyFrom::None)
|
||||
? SerializeFrom(pollVotes)
|
||||
: MTPReactionNotificationsFrom()),
|
||||
MTP_notificationSoundDefault(),
|
||||
MTP_bool(previews))
|
||||
)).done([=](const MTPReactionsNotifySettings &result) {
|
||||
_requestId = 0;
|
||||
apply(result);
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ApiWrap;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
enum class ReactionsNotifyFrom : uchar {
|
||||
None,
|
||||
Contacts,
|
||||
All,
|
||||
};
|
||||
|
||||
class ReactionsNotifySettings final {
|
||||
public:
|
||||
explicit ReactionsNotifySettings(not_null<ApiWrap*> api);
|
||||
|
||||
void reload(Fn<void()> callback = nullptr);
|
||||
|
||||
void updateMessagesFrom(ReactionsNotifyFrom value);
|
||||
void updatePollVotesFrom(ReactionsNotifyFrom value);
|
||||
void setAllFrom(ReactionsNotifyFrom value);
|
||||
void updateShowPreviews(bool value);
|
||||
|
||||
[[nodiscard]] ReactionsNotifyFrom messagesFromCurrent() const;
|
||||
[[nodiscard]] rpl::producer<ReactionsNotifyFrom> messagesFrom() const;
|
||||
[[nodiscard]] ReactionsNotifyFrom pollVotesFromCurrent() const;
|
||||
[[nodiscard]] rpl::producer<ReactionsNotifyFrom> pollVotesFrom() const;
|
||||
[[nodiscard]] bool showPreviewsCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> showPreviews() const;
|
||||
|
||||
[[nodiscard]] bool enabledCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> enabled() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPReactionsNotifySettings &settings);
|
||||
void save();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _requestId = 0;
|
||||
rpl::variable<ReactionsNotifyFrom> _messagesFrom
|
||||
= ReactionsNotifyFrom::All;
|
||||
rpl::variable<ReactionsNotifyFrom> _storiesFrom
|
||||
= ReactionsNotifyFrom::All;
|
||||
rpl::variable<ReactionsNotifyFrom> _pollVotesFrom
|
||||
= ReactionsNotifyFrom::All;
|
||||
rpl::variable<bool> _showPreviews = true;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_read_metrics.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSendTimeout = crl::time(5000);
|
||||
|
||||
} // namespace
|
||||
|
||||
ReadMetrics::ReadMetrics(not_null<ApiWrap*> api)
|
||||
: _api(&api->instance())
|
||||
, _timer([=] { send(); }) {
|
||||
}
|
||||
|
||||
void ReadMetrics::add(
|
||||
not_null<PeerData*> peer,
|
||||
FinalizedReadMetric metric) {
|
||||
_pending[peer].push_back(metric);
|
||||
if (!_timer.isActive()) {
|
||||
_timer.callOnce(kSendTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadMetrics::send() {
|
||||
for (auto i = _pending.begin(); i != _pending.end();) {
|
||||
if (_requests.contains(i->first)) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto metrics = QVector<MTPInputMessageReadMetric>();
|
||||
metrics.reserve(i->second.size());
|
||||
for (const auto &m : i->second) {
|
||||
metrics.push_back(MTP_inputMessageReadMetric(
|
||||
MTP_int(m.msgId.bare),
|
||||
MTP_long(m.viewId),
|
||||
MTP_int(m.timeInViewMs),
|
||||
MTP_int(m.activeTimeInViewMs),
|
||||
MTP_int(m.heightToViewportRatioPermille),
|
||||
MTP_int(m.seenRangeRatioPermille)));
|
||||
}
|
||||
const auto peer = i->first;
|
||||
const auto finish = [=] {
|
||||
_requests.erase(peer);
|
||||
if (!_pending.empty() && !_timer.isActive()) {
|
||||
_timer.callOnce(kSendTimeout);
|
||||
}
|
||||
};
|
||||
const auto requestId = _api.request(MTPmessages_ReportReadMetrics(
|
||||
peer->input(),
|
||||
MTP_vector<MTPInputMessageReadMetric>(std::move(metrics))
|
||||
)).done([=](const MTPBool &) {
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &) {
|
||||
finish();
|
||||
}).send();
|
||||
|
||||
_requests.emplace(peer, requestId);
|
||||
i = _pending.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
class ApiWrap;
|
||||
class PeerData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct FinalizedReadMetric {
|
||||
MsgId msgId = 0;
|
||||
uint64 viewId = 0;
|
||||
int timeInViewMs = 0;
|
||||
int activeTimeInViewMs = 0;
|
||||
int heightToViewportRatioPermille = 0;
|
||||
int seenRangeRatioPermille = 0;
|
||||
};
|
||||
|
||||
class ReadMetrics final {
|
||||
public:
|
||||
explicit ReadMetrics(not_null<ApiWrap*> api);
|
||||
|
||||
void add(not_null<PeerData*> peer, FinalizedReadMetric metric);
|
||||
|
||||
private:
|
||||
void send();
|
||||
|
||||
MTP::Sender _api;
|
||||
base::flat_map<
|
||||
not_null<PeerData*>,
|
||||
std::vector<FinalizedReadMetric>> _pending;
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _requests;
|
||||
base::Timer _timer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -332,7 +332,8 @@ void SendExistingPhoto(
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(0),
|
||||
photo->mtpInput(),
|
||||
MTPint());
|
||||
MTPint(),
|
||||
MTPInputDocument());
|
||||
};
|
||||
SendExistingMedia(
|
||||
std::move(message),
|
||||
@@ -377,7 +378,8 @@ void SendExistingPhoto(
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(0),
|
||||
photo->mtpInput(),
|
||||
MTPint());
|
||||
MTPint(), // ttl_seconds
|
||||
MTPInputDocument()); // video
|
||||
};
|
||||
SendExistingMedia(
|
||||
std::move(message),
|
||||
@@ -658,7 +660,8 @@ void SendConfirmedFile(
|
||||
MTP_flags(Flag::f_photo
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->photo,
|
||||
MTPint());
|
||||
MTPint(), // ttl_seconds
|
||||
MTPDocument()); // video
|
||||
} else if (file->type == SendMediaType::File) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
|
||||
@@ -264,6 +264,9 @@ EntitiesInText EntitiesFromMTP(
|
||||
d.vlength().v,
|
||||
SerializeFormattedDateData(d.vdate().v, flags),
|
||||
});
|
||||
}, [&](const MTPDmessageEntityDiffInsert &) {
|
||||
}, [&](const MTPDmessageEntityDiffReplace &) {
|
||||
}, [&](const MTPDmessageEntityDiffDelete &) {
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -257,7 +257,8 @@ void Transcribes::summarize(not_null<HistoryItem*> item) {
|
||||
: MTP_flags(MTPmessages_summarizeText::Flag::f_to_lang),
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id),
|
||||
langCode.isEmpty() ? MTPstring() : MTP_string(langCode)
|
||||
langCode.isEmpty() ? MTPstring() : MTP_string(langCode),
|
||||
MTPstring() // tone
|
||||
)).done([=](const MTPTextWithEntities &result) {
|
||||
const auto &data = result.data();
|
||||
auto &entry = _summaries[id];
|
||||
|
||||
@@ -42,6 +42,13 @@ bool UnreadThings::trackReactions(Data::Thread *thread) const {
|
||||
return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup());
|
||||
}
|
||||
|
||||
bool UnreadThings::trackPollVotes(Data::Thread *thread) const {
|
||||
const auto peer = thread ? thread->peer().get() : nullptr;
|
||||
return peer
|
||||
&& (peer->isChat() || peer->isMegagroup())
|
||||
&& !peer->isMonoforum();
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnough(Data::Thread *thread) {
|
||||
if (trackMentions(thread)) {
|
||||
preloadEnoughMentions(thread);
|
||||
@@ -49,6 +56,9 @@ void UnreadThings::preloadEnough(Data::Thread *thread) {
|
||||
if (trackReactions(thread)) {
|
||||
preloadEnoughReactions(thread);
|
||||
}
|
||||
if (trackPollVotes(thread)) {
|
||||
preloadEnoughPollVotes(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::mediaAndMentionsRead(
|
||||
@@ -84,6 +94,15 @@ void UnreadThings::preloadEnoughReactions(not_null<Data::Thread*> thread) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnoughPollVotes(not_null<Data::Thread*> thread) {
|
||||
const auto fullCount = thread->unreadPollVotes().count();
|
||||
const auto loadedCount = thread->unreadPollVotes().loadedCount();
|
||||
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
|
||||
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
|
||||
requestPollVotes(thread, loadedCount);
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::cancelRequests(not_null<Data::Thread*> thread) {
|
||||
if (const auto requestId = _mentionsRequests.take(thread)) {
|
||||
_api->request(*requestId).cancel();
|
||||
@@ -91,6 +110,9 @@ void UnreadThings::cancelRequests(not_null<Data::Thread*> thread) {
|
||||
if (const auto requestId = _reactionsRequests.take(thread)) {
|
||||
_api->request(*requestId).cancel();
|
||||
}
|
||||
if (const auto requestId = _pollVotesRequests.take(thread)) {
|
||||
_api->request(*requestId).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void UnreadThings::requestMentions(
|
||||
@@ -164,4 +186,38 @@ void UnreadThings::requestReactions(
|
||||
_reactionsRequests.emplace(thread, requestId);
|
||||
}
|
||||
|
||||
void UnreadThings::requestPollVotes(
|
||||
not_null<Data::Thread*> thread,
|
||||
int loaded) {
|
||||
if (_pollVotesRequests.contains(thread) || thread->asSublist()) {
|
||||
return;
|
||||
}
|
||||
const auto offsetId = loaded
|
||||
? std::max(thread->unreadPollVotes().maxLoaded(), MsgId(1))
|
||||
: MsgId(1);
|
||||
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
|
||||
const auto addOffset = loaded ? -(limit + 1) : -limit;
|
||||
const auto maxId = 0;
|
||||
const auto minId = 0;
|
||||
const auto history = thread->owningHistory();
|
||||
const auto topic = thread->asTopic();
|
||||
using Flag = MTPmessages_GetUnreadPollVotes::Flag;
|
||||
const auto requestId = _api->request(MTPmessages_GetUnreadPollVotes(
|
||||
MTP_flags(topic ? Flag::f_top_msg_id : Flag()),
|
||||
history->peer->input(),
|
||||
MTP_int(topic ? topic->rootId() : 0),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(limit),
|
||||
MTP_int(maxId),
|
||||
MTP_int(minId)
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
_pollVotesRequests.remove(thread);
|
||||
thread->unreadPollVotes().addSlice(result, loaded);
|
||||
}).fail([=] {
|
||||
_pollVotesRequests.remove(thread);
|
||||
}).send();
|
||||
_pollVotesRequests.emplace(thread, requestId);
|
||||
}
|
||||
|
||||
} // namespace UnreadThings
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool trackMentions(Data::Thread *thread) const;
|
||||
[[nodiscard]] bool trackReactions(Data::Thread *thread) const;
|
||||
[[nodiscard]] bool trackPollVotes(Data::Thread *thread) const;
|
||||
|
||||
void preloadEnough(Data::Thread *thread);
|
||||
|
||||
@@ -35,14 +36,17 @@ public:
|
||||
private:
|
||||
void preloadEnoughMentions(not_null<Data::Thread*> thread);
|
||||
void preloadEnoughReactions(not_null<Data::Thread*> thread);
|
||||
void preloadEnoughPollVotes(not_null<Data::Thread*> thread);
|
||||
|
||||
void requestMentions(not_null<Data::Thread*> thread, int loaded);
|
||||
void requestReactions(not_null<Data::Thread*> thread, int loaded);
|
||||
void requestPollVotes(not_null<Data::Thread*> thread, int loaded);
|
||||
|
||||
const not_null<ApiWrap*> _api;
|
||||
|
||||
base::flat_map<not_null<Data::Thread*>, mtpRequestId> _mentionsRequests;
|
||||
base::flat_map<not_null<Data::Thread*>, mtpRequestId> _reactionsRequests;
|
||||
base::flat_map<not_null<Data::Thread*>, mtpRequestId> _pollVotesRequests;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -91,6 +91,90 @@ enum class DataIsLoadedResult {
|
||||
Ok = 3,
|
||||
};
|
||||
|
||||
[[nodiscard]] bool PeerDataIsLoaded(
|
||||
not_null<Data::Session*> owner,
|
||||
PeerId peerId) {
|
||||
return !peerId || owner->peerLoaded(peerId);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MentionUsersDataIsLoaded(
|
||||
not_null<Data::Session*> owner,
|
||||
const MTPVector<MTPMessageEntity> &entities) {
|
||||
for (const auto &entity : entities.v) {
|
||||
auto loaded = true;
|
||||
entity.match([&](const MTPDmessageEntityMentionName &data) {
|
||||
loaded = owner->userLoaded(data.vuser_id());
|
||||
}, [&](const MTPDinputMessageEntityMentionName &data) {
|
||||
data.vuser_id().match([&](const MTPDinputUser &data) {
|
||||
loaded = owner->userLoaded(data.vuser_id());
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!loaded) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ForwardedInfoDataIsLoaded(
|
||||
not_null<Data::Session*> owner,
|
||||
const MTPMessageFwdHeader &header) {
|
||||
return header.match([&](const MTPDmessageFwdHeader &data) {
|
||||
return (!data.vfrom_id()
|
||||
|| PeerDataIsLoaded(owner, peerFromMTP(*data.vfrom_id())))
|
||||
&& (!data.vsaved_from_peer()
|
||||
|| PeerDataIsLoaded(owner, peerFromMTP(*data.vsaved_from_peer())))
|
||||
&& (!data.vsaved_from_id()
|
||||
|| PeerDataIsLoaded(owner, peerFromMTP(*data.vsaved_from_id())));
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool ReplyDataIsLoaded(
|
||||
not_null<Data::Session*> owner,
|
||||
const MTPMessageReplyHeader &header) {
|
||||
return header.match([&](const MTPDmessageReplyHeader &data) {
|
||||
return (!data.vreply_to_peer_id()
|
||||
|| PeerDataIsLoaded(owner, peerFromMTP(*data.vreply_to_peer_id())))
|
||||
&& (!data.vreply_from()
|
||||
|| ForwardedInfoDataIsLoaded(owner, *data.vreply_from()))
|
||||
&& (!data.vquote_entities()
|
||||
|| MentionUsersDataIsLoaded(owner, *data.vquote_entities()));
|
||||
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
||||
return PeerDataIsLoaded(owner, peerFromMTP(data.vpeer()));
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DataIsLoaded(
|
||||
not_null<Data::Session*> owner,
|
||||
const MTPDupdateShortMessage &data) {
|
||||
return owner->userLoaded(data.vuser_id())
|
||||
&& (!data.vfwd_from()
|
||||
|| ForwardedInfoDataIsLoaded(owner, *data.vfwd_from()))
|
||||
&& (!data.vvia_bot_id()
|
||||
|| owner->userLoaded(*data.vvia_bot_id()))
|
||||
&& (!data.vreply_to()
|
||||
|| ReplyDataIsLoaded(owner, *data.vreply_to()))
|
||||
&& (!data.ventities()
|
||||
|| MentionUsersDataIsLoaded(owner, *data.ventities()));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool DataIsLoaded(
|
||||
not_null<Data::Session*> owner,
|
||||
const MTPDupdateShortChatMessage &data) {
|
||||
return owner->chatLoaded(data.vchat_id())
|
||||
&& owner->userLoaded(data.vfrom_id())
|
||||
&& (!data.vfwd_from()
|
||||
|| ForwardedInfoDataIsLoaded(owner, *data.vfwd_from()))
|
||||
&& (!data.vvia_bot_id()
|
||||
|| owner->userLoaded(*data.vvia_bot_id()))
|
||||
&& (!data.vreply_to()
|
||||
|| ReplyDataIsLoaded(owner, *data.vreply_to()))
|
||||
&& (!data.ventities()
|
||||
|| MentionUsersDataIsLoaded(owner, *data.ventities()));
|
||||
}
|
||||
|
||||
void ProcessScheduledMessageWithElapsedTime(
|
||||
not_null<Main::Session*> session,
|
||||
bool needToAdd,
|
||||
@@ -1424,9 +1508,9 @@ void Updates::applyUpdates(
|
||||
|
||||
case mtpc_updateShortMessage: {
|
||||
auto &d = updates.c_updateShortMessage();
|
||||
if (!session().data().userLoaded(d.vuser_id())) {
|
||||
if (!DataIsLoaded(&_session->data(), d)) {
|
||||
MTP_LOG(0, ("getDifference "
|
||||
"{ good - getting user for updateShortMessage }%1"
|
||||
"{ good - after not all data loaded in updateShortMessage }%1"
|
||||
).arg(_session->mtp().isTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
@@ -1439,10 +1523,9 @@ void Updates::applyUpdates(
|
||||
|
||||
case mtpc_updateShortChatMessage: {
|
||||
auto &d = updates.c_updateShortChatMessage();
|
||||
const auto chat = session().data().chatLoaded(d.vchat_id());
|
||||
if (!chat) {
|
||||
if (!DataIsLoaded(&_session->data(), d)) {
|
||||
MTP_LOG(0, ("getDifference "
|
||||
"{ good - getting chat for updateShortChatMessage }%1"
|
||||
"{ good - after not all data loaded in updateShortChatMessage }%1"
|
||||
).arg(_session->mtp().isTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
@@ -1848,7 +1931,53 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
} break;
|
||||
|
||||
case mtpc_updateMessagePoll: {
|
||||
session().data().applyUpdate(update.c_updateMessagePoll());
|
||||
const auto &d = update.c_updateMessagePoll();
|
||||
const auto wasRecentVoters = session().data().pollRecentVoters(
|
||||
d.vpoll_id().v);
|
||||
session().data().applyUpdate(d);
|
||||
const auto notifyItem = session().data().findItemForPoll(
|
||||
d.vpoll_id().v);
|
||||
if (notifyItem) {
|
||||
CheckPollVoteNotificationSchedule(
|
||||
notifyItem,
|
||||
wasRecentVoters);
|
||||
}
|
||||
if (const auto tlPeer = d.vpeer()) {
|
||||
const auto &results = d.vresults();
|
||||
const auto hasUnread = results.match([](
|
||||
const MTPDpollResults &data) {
|
||||
return data.is_has_unread_votes();
|
||||
});
|
||||
const auto isMin = results.match([](
|
||||
const MTPDpollResults &data) {
|
||||
return data.is_min();
|
||||
});
|
||||
const auto peer = peerFromMTP(*tlPeer);
|
||||
const auto msgId = d.vmsg_id()->v;
|
||||
if (const auto history = session().data().historyLoaded(peer)) {
|
||||
if (const auto item = session().data().message(
|
||||
peer,
|
||||
msgId)) {
|
||||
if (hasUnread) {
|
||||
if (!item->hasUnreadPollVote()) {
|
||||
item->setHasUnreadPollVote();
|
||||
item->addToUnreadThings(
|
||||
HistoryUnreadThings::AddType::New);
|
||||
}
|
||||
} else if (!isMin && item->hasUnreadPollVote()) {
|
||||
item->markPollVotesRead();
|
||||
}
|
||||
} else {
|
||||
if (history->unreadPollVotes().has()) {
|
||||
if (hasUnread) {
|
||||
history->unreadPollVotes().checkAdd(msgId);
|
||||
}
|
||||
}
|
||||
history->owner().histories().requestDialogEntry(
|
||||
history);
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateUserTyping: {
|
||||
|
||||