how fork code, commits and patches should look. the goal is a queue that reads well, reviews quickly and survives every upstream release with as few conflicts as possible.
-
new code goes into new files under
Telegram/SourceFiles/tele/, namedtele_<feature>.cppandtele_<feature>.h, innamespace Tele. add every new file to the telenice_target_sourcesblock inTelegram/CMakeLists.txt. -
settings rows go into
settings/settings_tele.cpp. -
upstream files only get hunks that hook into
Tele::: an include, a call, an early return, a changed argument. a typical hunk:#include "tele/tele_send_files.h" ... - auto result = Core::App().settings().sendFilesWay(); + auto result = Tele::DefaultSendFilesWay(); -
put new
#include "tele/..."lines next to the tele includes already in the file, if there are any. -
prefer hooks over copies. call the upstream function and adjust its input or result from
Tele::, or use an existing virtual hook, instead of copying an upstream function into fork code. a copy silently misses every upstream fix. -
avoid widely included upstream headers (
data/data_peer.h,history/history_item.h, anything in the precompiled header). a new member or include there rebuilds most of the tree for everyone and conflicts more often. keep state in fork code, keyed by the upstream object if needed. -
changes to a submodule (
Telegram/lib_ui,Telegram/lib_baseand so on) are committed inside the submodule and exported intopatches/<submodule path>/. only do that when the tdesktop side can't do it.
follow upstream's style, since fork code sits next to it:
- tabs for indentation, the formatting of the surrounding upstream code.
[[nodiscard]]on functions that return a value,const autoby default,u"..."_qforQStringliterals,not_null<>for pointers that can't be null.rplfor reactive values,crl::async/crl::on_mainfor threading, as upstream does.- types and functions in
PascalCase, locals incamelCase, members with a leading_. #pragma oncein headers, namespaces closed with} // namespace Tele.- no comments. names and small functions carry the meaning. the one exception is a short note on a real trap that the code can't express.
every tele setting is a base::options option. adding a switch touches four places:
-
tele/tele_options.h: declare the id and the getter.extern const char kOptionHdPhotos[]; [[nodiscard]] bool HdPhotos();add an
rpl::producergetter as well when the ui has to react while the option changes. -
tele/tele_options.cpp: define the id and the option, in the anonymous namespace, and the getter.const char kOptionHdPhotos[] = "tele-hd-photos"; base::options::toggle HdPhotosOption({ .id = kOptionHdPhotos, .name = "Send photos in HD by default", .description = "Every send box starts with HD quality on, whatever was " "picked last time.", .defaultValue = true, }); bool HdPhotos() { return HdPhotosOption.value(); }- ids are
tele-plus kebab case, constants arekOptionplus pascal case. - toggles default to off. only set
.defaultValue = truewhen the maintainer asked for it. .restartRequired = truewhen the change only takes effect after a restart. the settings row then offers one.- strings use
base::options::option<QString>, with aSet...function and a...Value()producer next to the getter.
- ids are
-
settings/settings_tele.cpp: show it on the right page, in a fitting group.AddOptionToggle(builder, ::Tele::kOptionHdPhotos);an optional third argument adds search keywords, e.g.
AddOptionToggle(builder, id, { u"quality"_q }). a row that only matters while another option is on takes that option as the fourth argument, e.g.AddOptionToggle(builder, id, {}, ::Tele::kOptionQueueBehindMedia), and stays hidden until it's on.the pages are built by
FillInterface,FillChats,FillPrivacy,FillProfiles,FillBotsandFillDebug, the root page byFillRoot. each page is split into groups withAddGroupTitleandAddGroupDivider. put a new row into the group it belongs to, or start a new group. never append a loose row to the end of a page. the description and the optional keywords feed both searches, so add keywords for words people would search for that aren't in the name or description. -
tele/tele_settings_io.cpp: add it to theSpecs()table, so settings export and import know it.Toggle(id)for switches,Choice,Text,People,DeviceorInternalfor the rest (see architecture).
upstream call sites then only call the getter, e.g. if (Tele::HdPhotos()) ....
off by default has to mean exactly upstream's behaviour. with the option off, the hunk must not change anything a user can see or anything that goes over the network.
- tele's strings are hardcoded english. don't add keys to
lang.strings. - write them in sentence case and pass every one through
Tele::Lower(tele/tele_lowercase.h) where it's shown, so the lowercase option covers it. option names and descriptions shown throughAddOptionToggleare already handled. - a description only when the name can't fully explain the item, for options, rows and the texts under settings groups alike. if the name says it all, leave it out. when it's needed: one short sentence with the missing fact (a side effect, a condition, what gets sent where). no filler ("this lets you…", "when enabled…"), no restating the name, no lists of examples.
- call the app tele, and the service telegram.
- one line, conventional prefix, lowercase, imperative, no trailing period:
feat: send photos in hd by default,fix: ignore reply counters on scheduled messages. - no body, no trailers, no co-author lines, no tool or ai attribution.
- in the tdesktop tree the subject becomes the patch subject and its file name, and ci matches patches across releases by subject. describe what the user gets, and don't rename a released patch without a reason: a new subject shows up as one dropped and one new patch in the release notes.
- in the tele repository the same format applies, with the usual prefixes:
feat:,fix:,docs:,ci:,chore:.
- one feature per patch. fixes to an unreleased feature go into its commit (
git commit --fixup,git rebase -i --autosquash). - small hunks survive upstream syncs. every extra context line is a chance to conflict.
- don't reformat, reorder or rename upstream code, and don't fix upstream whitespace in passing.
- keep the line endings of the file you edit. a windows checkout with
core.autocrlf=truehas crlf in the tdesktop tree: new files there get crlf too, and a file must never mix both. - never edit a
.patchfile by hand. change the commits in the tdesktop tree and runpython tele.py export. - never bump
UPSTREAMin a feature change. moving to a new upstream tag is its own change. - never copy upstream's
.github/or any other part of the upstream tree into this repository. its workflows would start running here.
tele is a client for people who care what leaves their machine.
- no new third-party network requests without a setting that controls them. the setting defaults to off.
- every network request tele adds checks the launch flags at its lowest level:
Tele::OfflineForLaunch()for third-party services,Tele::ServerOffForLaunch()for the tele server,Tele::UpdatesOffForLaunch()for github updates and what's new. update launch flags when a flag starts covering something new. - never send account data to the tele server: no ids, usernames, contacts or messages. the client downloads a public list and does the matching locally. the only exception is a crash report, which leaves only after the user clicks send, shows its text first and lets the user untick their username.
- the tele server protocol is public in tele server. a change to it is a documented, backwards compatible change there, not a hidden one in the code.