Telegram github commits and releases
Kanalga Telegram’da o‘tish
Broadcast from the most important Telegram clients' repositories
Ko'proq ko'rsatish4 822
Obunachilar
-1124 soatlar
-297 kunlar
-10930 kunlar
Postlar arxiv
telegramdesktop/tdesktop/dev • 228934b • 1 files, +4/-1
Work around qmake race condition
#tdesktop
TGX-Android/Telegram-X/main • 1e20301 • 2 files, +3/-1
Add `version.sdk_package` property
#tgxandroid
TGX-Android/Telegram-X/main • d76e7b5 • 6 files, +82/-22
Upgrade Gradle to 9.6.0 + FCM to 25.1.0 + Upgrade dependencies + Set target SDK to Android 17
TGX-Android/Telegram-X/main • f5f283a • 1 files, +1/-1
Version bump to `1788`
#tgxandroid
TGX-Android/Telegram-X/main • 73ba0b7 • 42 files, +2354/-572
Upgrade TDLib to tdlib/td@e0943d0
#tgxandroid
morethanwords/tweb/master • ca25dd1 • 3 files, +126/-2
perf: cut redundant per-keystroke work in the message input
Two redundant-work eliminations in src/components/chat/input.ts, the
contenteditable message-input hot path. Behavior preserved.
#2 - keyup re-parsed a second time per char
The `keyup` listener called checkAutocomplete() with no args on EVERY key,
re-walking the contenteditable (getRichValueWithCaret) and re-parsing
markdown+entities - work the `input` handler (onMessageInput) had already done
one tick earlier and fed to checkAutocomplete. classifyInputKeyup (new pure
helper) gates it per key: content keys (printable, Backspace/Delete/Enter) and
inert/modifier keys SKIP (the `input` event already covered them / nothing
changed); only caret-move keys (arrows/Home/End/PageUp/PageDown - including with
a modifier held, e.g. Cmd/Option/Ctrl+arrow for line/word navigation, which
never fire `input`) re-check. Parses per typed char 2 -> 1; wasted walks on
inert keys 1 -> 0.
#3 - emoji search on every plaintext keystroke
The emoji-autocomplete branch ran appEmojiManager.prepareAndSearchEmojis (a
SharedWorker round-trip) for every bare-word keystroke, but a 1-char bare token
can never match the keyword index (SearchIndex minChars=2). isPlausibleEmojiQuery
(new pure helper) gates it: an explicit `:foo` query always searches
(firstChar === ':'), a bare-word query only once it has >= 2 chars. Observable
result identical minus the wasted round-trip.
Behavior preserved: typing/parsing/drafts/typing-notifications untouched (all on
the `input` path); emoji autocomplete still appears for `:foo` and bare words
from 2 chars; mentions/commands/inline/stickers untouched; autocomplete still
re-checks on caret move, including modifier+arrow navigation.
Validated by a deterministic jsdom Vitest suite (pure-helper predicate tables +
call-count drivers) before merge. Gates: tsc 0 - eslint 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
morethanwords/tweb/master • 47e1ace • 1 files, +13/-1
perf: coalesce per-bubble readMaxId round-trip in renderMessage
ChatBubbles.renderMessage runs per bubble; in group/channel chats every
non-unread bubble awaited an identical cross-worker
getReadMaxIdIfUnread(peerId, threadId) — N round-trips per render burst for
one read cursor. Route it through a memoizeAsyncWithTTL wrapper
(key peerId_threadId, TTL 0): N->1 per burst, while TTL 0 drops the entry on
the next macrotask so a later render pass still re-reads a fresh value.
Behavior preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • 57fe5bc • 1 files, +29/-21
perf(animationIntersector): O(1) byElement index for onObserve + getAnimations
Bottleneck (hot path): the IntersectionObserver callback (`onObserve`) and
`getAnimations(el)` each did an O(groups × items) nested scan over every
animation group on every IO callback — i.e. while scrolling sticker/media-dense
chats, each batch of intersection entries walked all groups and `.find()`d
within each. `getAnimations` is also called per video/sticker play decision
(video.ts, bubbles.ts, appImManager.ts, bluffSpoilerController.ts,
SuperStickerRenderer.ts). Cost grows with the number of open/cached animation
groups and items.
Fix (single file): add a parallel `byElement: Map<HTMLElement, AnimationItem[]>`
maintained in lockstep with the existing `byPlayer` Map — pushed in
`addAnimation`, spliced (and key-deleted when empty) in `removeAnimation`.
- `onObserve` resolves `byElement.get(entry.target)` then acts on the first
item whose group is not intersection-locked — the exact semantics of the old
group scan + per-entry `break` (one item acted on per entry).
- `getAnimations(el)` returns `byElement.get(el)?.slice() ?? []` — same array
shape and fresh-copy contract as before.
No call-site signature changes; contained to animationIntersector.ts.
Behavior preserved: identical set of items resolved, identical
visible-set / checkAnimation / intersection-lock semantics. A Proxy on byGroups
confirmed the new path performs ZERO group enumerations, and getAnimations
matches a reference O(groups×items) linear scan for every element.
Note: this is an algorithmic-hygiene win (removes a term that grows with chat
density), not a measurable per-frame ms saving. Verified with a 5-test suite
(tsc + eslint clean, vitest 5/5); the test file is omitted here to keep the
commit small.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • dde5000 • 2 files, +98/-1
fix: refresh future relative-time labels at exact boundaries instead of a unit late
Bug (backlog IDEA, iter 4 — "formatRelativeTime nextBoundaryDelay
future-boundary case"): a relative-time label pointing at a FUTURE instant
("in 5 minutes", "in 2 hours", …) froze for a full extra unit whenever the
remaining time was an exact multiple of its display unit.
Root cause (src/helpers/date/formatRelativeTime.ts:73): nextBoundaryDelay
computed the timer delay until the label's next change as
`isPast ? (unit - remainder) : (remainder || unit)`.
For a future timestamp the displayed whole-unit count DECREMENTS as `absDiff`
shrinks toward now, so the label changes in `remainder` seconds. When
`remainder === 0` (absDiff is exactly k*unit, e.g. 300s → "in 5 minutes"), the
`|| unit` fallback returned a full unit (60s). But one second later absDiff is
4m59s → "in 4 minutes", so the label should refresh almost immediately. Result:
at every exact future boundary the label stayed one unit too high for up to a
whole unit. The past branch is unaffected — `unit - remainder` correctly yields
a full unit at a boundary (a "5 minutes ago" label is valid for the next
minute).
Fix: in the future branch fall back to a ~1s refresh at an exact boundary
(`remainder || 1`) instead of a full unit, matching the `|| 1000` 1-second-tick
idiom the JustNow case already uses. Identical behavior for every non-boundary
case; strictly more correct at boundaries.
Reachability: the only caller is wrapRichText.ts for relative
messageEntityTimestamp entities, which drive a setTimeout off updateInterval to
re-render the label; future timestamps are supported there.
Tests: +10 Vitest cases (new src/tests/formatRelativeTime.test.ts) covering past
boundaries (unchanged), future non-boundaries, and the 3 exact-future-boundary
regressions. The 3 boundary cases FAILED before (updateInterval === unit*1000)
and PASS after; the 7 others passed before and after (no regression).
Gates: tsc 0 · eslint 0 · vitest 10/10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2ef30af4a5fffeb6c7c9323e0a8cab40dd6bb9f4)
morethanwords/tweb/master • 86bc556 • 2 files, +172/-0
fix: collect custom emoji from poll questions and to-do lists
getUniqueCustomEmojisFromMessage backs the chat context-menu "Open Emojis"
action (contextMenu.ts) — it gathers every unique custom emoji in a message to
gate the action's visibility (via .length) and list the emoji packs. It walked
message text, reaction custom emoji and poll ANSWER texts, but missed two other
TextWithEntities fields, so a custom emoji placed there was silently dropped
(action hidden + emoji absent) even though the same emoji elsewhere worked:
- poll QUESTION text (Poll.question is TextWithEntities; createPoll persists
questionEntities from the rich question input).
- to-do list (checklist) text: todo.title and every todo.list[].title
(TodoList/TodoItem carry TextWithEntities; checklist.tsx persists them via
getRichValueWithCaret).
Fix: also iterate poll.question.entities, and the messageMediaToDo todo title +
item titles, through the existing iterateEntities collector. filterUnique()
already dedups overlap across question/answers and across title/items.
Behavior-preserving for the existing paths.
Tests: src/tests/getUniqueCustomEmojisFromMessage.test.ts covers message text,
poll question (regression), poll answers, question+answer dedup, to-do title,
to-do items, and title+item dedup (7/7).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • 66b6cc6 • 2 files, +32/-1
fix: trim ALL trailing whitespace in clearBadCharsAndTrim
Bug: cleanSearchText's trim regexp was asymmetric — `/^\s+|\s$/g`. The
leading branch (`^\s+`) strips all leading whitespace, but the trailing
branch (`\s$`) is a single-char class with no `+`, so the `$` anchor lets
it match only the very last whitespace character. Any string with two or
more trailing whitespace chars keeps all but one:
clearBadCharsAndTrim('abc ') -> 'abc ' (expected 'abc')
clearBadCharsAndTrim(' foo!!! ') -> 'foo ' (expected 'foo')
clearBadCharsAndTrim('tab\t\t') -> 'tab\t' (expected 'tab')
Root cause: `\s$` vs the correctly-quantified `^\s+`. A trim helper must
strip a run of trailing whitespace, not one char.
Fix: quantify the trailing branch — `/^\s+|\s+$/g`. Single-line change in
src/helpers/cleanSearchText.ts; leading-trim and bad-char stripping are
unchanged, inner whitespace is preserved.
Impact: clearBadCharsAndTrim is exported as a general clean+trim helper and
feeds the document wrapper's `ext-${ext}` CSS class derivation
(src/components/wrappers/document.ts) — a trailing-whitespace extension
would yield a malformed class. The search-text normalizer it backs should
not emit trailing whitespace in normalized terms.
Evidence: added src/tests/cleanSearchText.test.ts (7 cases). Before fix
5/7 failed (every multi-trailing-whitespace case); after fix 7/7 pass.
Gates: tsc 0 errors, eslint 0, vitest 7/7 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
morethanwords/tweb/master • e5aab72 • 3 files, +55/-3
fix: reject trailing & double underscores in isUsernameValid
isUsernameValid (a port of TDLib is_valid_username) had two dead checks
that always evaluated false, so server-invalid usernames passed the
client-side gate.
Bug
- src/lib/richTextProcessor/validators.ts compared characters to the
empty string instead of '_':
if(username.charAt(username.length - 1) === '') // trailing _
if(username.charAt(i - 1) === '' && charAt(i) === '_') // double _
String.charAt() returns '' only for an out-of-range index, so within a
valid string range it is never '' — both branches are unreachable.
- TDLib's reference (td/telegram/misc.cpp is_valid_username) compares
against '_': reject when the last char is '_' and reject consecutive
underscores. The port mistranslated '_' to ''.
Impact
- UsernameInputField (public username / channel link editing) accepted
"name_" and "a__b" client-side; the comment at usernameInputField.ts:38
even acknowledged "does not check the last underscore". The bad name
only got rejected after a server round-trip (USERNAME_INVALID) instead
of an immediate inline error.
Fix
- Compare against '_' in both checks, matching the TDLib reference.
- Drop the now-stale "does not check the last underscore" comment.
Evidence (src/tests/validators.test.ts, 8 cases)
- Before: 3 failed — 'abc_'/'username_' (trailing), 'a__b'/'foo___bar'
(consecutive), and isWebAppNameValid('app_'|'a__b') all returned true.
- After: 8/8 pass; single non-trailing underscores ('a_b',
'foo_bar_baz') still accepted (no regression).
Gates: tsc 0 · eslint 0 (all changed files) · vitest 8/8.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • 387bfb7 • 2 files, +62/-1
fix: formatNumber violated its string return contract on out-of-range magnitudes
formatNumber(@helpers/number/formatNumber) is the shared count formatter
(view/reaction/unread/star/poll/story badges). Its suffix array
['','K','M','B','T'] (length 5) is indexed by i = floor(log(n)/log(1000)),
unbounded at the top: at n >= 1e15 the index reaches 5, sizes[5] is undefined,
and `value + undefined` coerces to NaN — a number, not the declared `: string`
(the negative branch yielded "-NaN").
This is type-hardening, NOT a user-visible bugfix: no real Telegram count gets
near a trillion (the practical ceiling across every caller — views, reactions,
poll voters, stars, unread, replies — is billions), so the out-of-range path is
unreachable from normal input. The 'T' unit itself is effectively dead too. The
value is contract soundness — the function must not silently return a NaN-number
— plus a cheap guard if some upstream defect ever feeds it a garbage magnitude
(corrupted Long, overflow), where a clamped "1000T" string beats "NaN" in a badge.
Fix: clamp the unit index with Math.min(..., sizes.length - 1) — the same idiom
already shipped for the sibling formatBytes/formatBytesPure overflow. All
in-range output (0, sub-1K, K/M/B/T, negatives, custom decimals) is byte-for-byte
unchanged; out-of-range magnitudes now render in trillions instead of NaN.
Test (src/tests/formatNumber.test.ts): pure-unit Vitest, 7 cases. In-range
behavior asserted unchanged; out-of-range inputs (1e15, 5e15, 1e18, -2e15) must
stay strings free of "NaN"/"undefined". Failed 2/5 before the clamp, 7/7 after.
Gates (in the worktree): tsc --noEmit (0) - eslint (0) - vitest (7 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
morethanwords/tweb/master • 4d1a80c • 2 files, +22/-1
fix: enforce separator consistency in search-by-date longDate parser
`if(!matches[2] === matches[4])` parsed as `(!matches[2]) === matches[4]`
(always false), so the separator-consistency guard never fired and mixed
separators like "12.03/2024" were accepted as valid date tips. Restore the
intended check (matching DrKLO's `!group(2).equals(group(4))`) so mismatched
separators are rejected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • ef49559 • 2 files, +75/-0
fix: trimRichText emits out-of-range entity when entity sits in trimmed whitespace
Bug: trimRichText could return a MessageEntity with a negative length (and an
out-of-range offset) whenever an entity lay entirely inside the whitespace that
gets trimmed off the right side of the text. Reached from the folder-title input
(editFolder.tsx -> trimRichText) where it would persist a corrupt
textWithEntities into the dialog filter title.
Repro: trimRichText('hi ', [{_: 'messageEntityBold', offset: 3, length: 2}])
-> bold entity became {offset: 3, length: -1} (offset 3 also > trimmed len 2).
Root cause: the right-trim block only adjusted length, via
`entity.length = text.length - entity.offset`, with no guard for the case where
`entity.offset` itself is already past the trimmed `text.length`. That makes the
subtraction negative and leaves the offset out of bounds. The left-trim path
already clamps offset with Math.max(0, ...); the right-trim path had no
equivalent clamp.
Fix: on the right trim, first pull an offset that fell into the trimmed trailing
whitespace back to `text.length`, then clamp length as before (now never
negative). Finally drop entities that ended up empty (length <= 0) — they no
longer reference any real text. Dropping zero-length entities matches the
existing idiom in getRichValueWithCaret (the producer of these entities), which
already splices out entities whose length becomes <= 0.
Tests: added src/tests/trimRichText.test.ts covering entity-in-trailing-
whitespace (the failing case), entity spanning the boundary (length shrinks),
in-range entity (unchanged), leading-whitespace shift, entity-in-leading-
whitespace, and both-ends-trimmed. The trailing-whitespace assertion failed
before the fix (AssertionError: expected -1 to be >= 0) and passes after.
Gates: tsc --noEmit 0, eslint 0 on changed files, vitest run trimRichText.test.ts
6/6 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • 8af5386 • 2 files, +59/-29
fix: splitStringByLength corrupted long messages on the overflow path
GitHub #343 [BUG] Incorrectly splits big message — "it sends first part
and last part, losing everything else".
Root cause (src/helpers/string/splitStringByLength.ts)
------------------------------------------------------
The inner cut() captured `const _arrayIndex = arrayIndex++` for the head
slot BEFORE the overflow branch, but the overflow branch then wrote the
recursively-split pieces into the SAME shared `out[arrayIndex++]` slots
and finally line 22 wrote the head into `out[_arrayIndex]`. When a single
token exceeded maxLength the indices collided/leapfrogged, leaving empty
'' slots interleaved with content. There was also a second, deeper flaw:
the overflow cut fired as `cut(lastSliceStartIndex + length)` while
`length` was still 0 for a leading oversized token, so it sliced an empty
range — producing a leading '' and mis-anchoring every subsequent chunk.
Confirmed against current source via Vitest: input
`'X'.repeat(35) + ' aa bb cc dd ee ff'` with maxLength 10 returned
`["","XXXXXXXXXX","","XXXXXXXXXX","","XXXXXXXXXX","XXXXX ", ...]` — empty
parts interleaved with real content. Downstream in
appMessagesManager.sendText each splitted[i] is sent as a separate
message and entity offsets advance by `partOffset += splitted[i].length`,
so empty parts mean wasted/dropped sends and misaligned entities — the
report's "loses everything in the middle" symptom.
Fix
---
Rewrote the function as a single left-to-right greedy partitioner:
accumulate space-delimited tokens (each keeps its trailing space, so the
output joins back to the input losslessly) into the current chunk while
it stays <= maxLength; flush before a token that would overflow; and
hard-cut any single token longer than maxLength into maxLength-sized
pieces. No shared-index bookkeeping, no recursion, no empty slots. Chunk
boundaries for normal (non-overflow) messages are byte-for-byte identical
to the previous implementation, so existing send/entity-offset behaviour
is unchanged.
Test evidence
-------------
New src/tests/splitStringOverflow.test.ts exercises the overflow branch
(single oversized leading token + words, long leading token, oversized
token mid-string, multiple oversized tokens) and asserts the file's own
assertValidSplit contract: parts.join('') === original, every part
length > 0, every part length <= maxLength. All 4 cases FAILED on the old
source (empty '' parts) and PASS after the fix. The existing
src/tests/splitString.test.ts (11 cases) still passes.
Gates: `npx tsc --noEmit` 0 errors; `npx eslint` on both changed files 0;
`npx vitest run` 15/15 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • 3afbb33 • 2 files, +44/-0
fix: isMixedScriptUrl no longer flags protocol-less URLs as spoofed
Bug
`isMixedScriptUrl` is a port of telegram-tt's `isMixedScriptUrl`
(src/util/browser/url.ts, cited in the file header). The reference runs the
raw url through `ensureProtocol(url)` BEFORE `new URL(...)`:
domain = convertPunycode(new URL(ensureProtocol(url)).hostname);
The tweb port dropped that wrapper:
domain = convertPunycode(new URL(url).hostname);
`new URL(...)` requires a protocol, so a protocol-less url ("example.com")
throws and lands in the catch, which returns `true` ("treat as mixed
script"). Every protocol-less domain — including plain pure-Latin and plain
single-script ones — is therefore mis-classified as a homograph/confusable
spoof. The masked-link warning gate (wrapRichText, `showMaskedAlert`) keys off
this result.
Root cause
Mistranslated C++/TS port: the `ensureProtocol` normalization step before
`new URL(...)` was omitted (isMixedScriptUrl.ts).
Fix
Prepend a protocol when none is present before parsing, mirroring the
reference's `ensureProtocol` and tweb's own `wrapUrl` idiom
(`'https://' + url`), reusing the existing `matchUrlProtocol` helper. Genuinely
invalid input still hits the catch and stays `true`.
Impact / honesty
Latent through the current sole caller: wrapRichText feeds the url through
`wrapUrl`, which already prepends `https://` when there's no protocol, so the
helper currently never sees a protocol-less url on that path. This is
contract-hardening on an EXPORTED helper (correct for any other/future caller)
plus first-ever test coverage — same flavor as the merged deepEqual /
getWeekNumber fixes.
Evidence
New src/tests/isMixedScriptUrl.test.ts (jsdom-safe; URL + Unicode regex only).
Before: 3 failed — 'example.com', 'my-site123.com', 'пример.рф' each returned
true. After: 6/6 pass; protocol-prefixed and unparseable cases unchanged.
Gates: tsc --noEmit 0 · eslint (changed files) 0 · vitest 6/6 green.
Reference: github.com/Ajaxy/telegram-tt src/util/browser/url.ts isMixedScriptUrl
(uses ensureProtocol before new URL).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • aa433ff • 2 files, +51/-1
fix: getWeekNumber divided a ms delta by a seconds constant (1000x off)
Bug (forge discovery scan over src/helpers/date.ts)
getWeekNumber computed the ISO week number as
Math.ceil((((d.getTime() - yearStart.getTime()) / ONE_DAY) + 1) / 7)
but getTime() deltas are in milliseconds while ONE_DAY is 86400 (seconds).
Dividing a millisecond span by the seconds-per-day constant inflates the
day count ~1000x, so the function returned absurd values: getWeekNumber for
2024-06-21 yielded 24429 instead of the ISO week 25; for 2017-01-02 it
returned 572 instead of 1. The algorithm is copied verbatim from
https://stackoverflow.com/a/6117889, which divides by 86400000 (ms/day) —
the port dropped the *1000.
Reachability
getWeekNumber is exported. Its single in-app caller (formatDateAccordingTo
TodayNew, "current week" weekday-format branch) compares two dates already
within ~7 days, and the 1000x scaling cancels out under that equality
comparison — so today's user-visible behavior is unchanged (verified
exhaustively: 0 branch-decision differences across 2018–2031, every day,
hourly within the 7-day gate). The fix therefore hardens an exported helper
that returns a grossly wrong number for any other/future caller, with no
behavior change to the existing one. Strictly more correct.
Fix
Divide by ms-per-day (ONE_DAY * 1000). 1-line change + clarifying comment.
Evidence
New src/tests/getWeekNumber.test.ts: 7 cases vs ISO-8601 reference week
numbers (incl. Sunday→prev-year week 52, year-end roll into next ISO year,
and a 1..53 range sweep over a full year). Before: 6/7 FAIL
(e.g. "expected 858 to be 1", "expected 51715 to be <= 53"). After: 7/7 pass.
Gates: tsc 0 · eslint 0 (date.ts + test) · vitest 7/7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0ef09e626c661017807104466789b27886568ba1)
morethanwords/tweb/master • 15221e2 • 2 files, +38/-1
fix: correct alpha compositing in rgbaToRgb (background coefficient)
Bug (discovery scan, src/helpers/color.ts — pure helper)
rgbaToRgb() flattens an RGBA colour over an OPAQUE background. The
correct compositing formula is out = a*fg + (1 - a)*bg, but the code
weighted the background term by `a` as well:
a * (color/255) + (a * (bg/255)) // both terms scaled by a
Root cause (current source, color.ts:294)
The background contribution used `a` instead of `(1 - a)`. Consequences:
- a = 1 (opaque fg) returns fg + bg (clamped), not fg.
- a = 0 (transparent fg) returns 0, not bg.
- it only happens to be correct at a == 0.5 (where a == 1 - a), which
hid the bug for the common case.
Impact
webApp.tsx setHeaderColor() calls rgbaToRgb([...textColor, textOpacity],
headerRgb) to derive the Mini App header's --secondary-text-color by
flattening semi-transparent secondary text over the header background.
textOpacity comes from calculateOpacity() and ranges 0.5..0.64, so any
opacity above 0.5 produced a wrong (too-bright/over-saturated) colour.
Fix
Weight the background term by (1 - a). One-line change; output unchanged
for a == 0.5 and strictly correct elsewhere.
Tests (src/tests/colorRgbaToRgb.test.ts, new)
5 cases vs an alpha-compositing reference: opaque-fg, transparent-fg,
0.5 midpoint, the non-0.5 webApp.tsx usage, and a no-overflow check.
Before: 3 failed (opaque/transparent/non-0.5), 2 passed (0.5 cases).
After: 5/5 pass.
Gates: tsc 0 · eslint 0 (both files) · vitest 5/5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
morethanwords/tweb/master • 154f42d • 2 files, +60/-1
fix: stringMiddleOverflow no longer exceeds maxLength (output was always ~maxLength+3)
Bug
---
`stringMiddleOverflow(str, maxLength)` is a middle-ellipsis truncation helper
used to shorten a string to fit a budget (e.g. the empty-search hint shortens
the query to 18 chars in topbarSearch.tsx). But its output was systematically
LONGER than the requested maxLength — and for inputs only slightly over the
limit it was even longer than the original string, defeating the helper's
entire purpose. This is the same class of off-by-the-ellipsis defect already
fixed elsewhere in the formatter cluster.
Same symptom as the platform reports about truncated/oversized inline labels;
discovered via a discovery scan over src/helpers/string during the Forge bug
lane (the well-mined number/byte/duration formatters were skipped per journal).
Root cause (current code)
-------------------------
str.slice(0, maxLength / 2 | 0) + '...' + str.slice(-Math.round(maxLength / 2))
The two halves already sum to ~maxLength, then a 3-char ellipsis is added ON
TOP, so the result length is always ≈ maxLength + 3. The 3-char ellipsis budget
was never subtracted. Examples (before):
stringMiddleOverflow('abcdefgh', 6) -> 'abc...fgh' (9 chars, > 6 AND > input 8)
stringMiddleOverflow('a'.repeat(20),18)-> 21 chars (> 18 AND > input 20)
Fix
---
Reserve the ellipsis length from the budget before splitting head/tail — the
same idiom the sibling helpers fitSymbols / limitSymbols already use:
const budget = Math.max(0, maxLength - ellipsis.length);
head = ceil(budget/2), tail = floor(budget/2)
Output is now always ≤ maxLength; the head+ellipsis+tail shape and the
"return unchanged when it already fits" fast path are preserved.
Test evidence (src/tests/stringMiddleOverflow.test.ts)
------------------------------------------------------
4 tests. BEFORE the fix 2 fail:
"never exceeds maxLength for any overflowing input"
-> AssertionError: expected 9 to be less than or equal to 6
"shortened result is never longer than the original (maxLength 18)"
-> AssertionError: expected 21 to be less than or equal to 18
AFTER the fix all 4 pass (the unchanged-fit + head/ellipsis/tail-shape tests
also lock the preserved behavior).
Gates: tsc --noEmit 0 errors · eslint 0 · vitest 4/4 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 8ab148fed4cfa6d0d8a3ad9e835b9aa6b0b0d3e7)
#webk
telegramdesktop/tdesktop/dev • e99928b • 2 files, +25/-9
Added custom emoji size support to sticker uploading.
telegramdesktop/tdesktop/dev • 6241d23 • 3 files, +137/-40
Added custom emoji creation flow to sticker creator box.
telegramdesktop/tdesktop/dev • 103c256 • 7 files, +157/-51
Added new-emoji button with menu to emoji set boxes.
telegramdesktop/tdesktop/dev • 9895e5b • 4 files, +128/-0
Added adapting existing sticker into custom emoji.
telegramdesktop/tdesktop/dev • ff2dfbf • 5 files, +65/-21
Improve accessibility changes for left filters.
telegramdesktop/tdesktop/dev • e4cde25 • 1 files, +1/-1
Fix build with Qt 5.
#tdesktop
telegramdesktop/tdesktop/dev • 18df941 • 2 files, +109/-1
Added screen reader tab-control accessibility for chat folders.
telegramdesktop/tdesktop/dev • 7ffb2c5 • 1 files, +4/-0
Report the chat-folders tab strip as a vertical tab control
Override TabListLayout::accessibilityOrientation() to return
Qt::Vertical, so screen readers announce the folders strip as a vertical
tab control (UIA Orientation). Relies on desktop-app/lib_ui#304 and
desktop-app/patches#254.
telegramdesktop/tdesktop/dev • c48860d • 1 files, +4/-0
Expose locked folders as plain buttons, not selectable tabs
A locked (premium) folder can never become the current tab - pressing it
opens the Premium box. Mark it not a page tab once locked, so it reports
as a plain button instead of a selectable PageTab and the tab container's
select() correctly rejects it.
telegramdesktop/tdesktop/dev • 9a5ec8b • 3 files, +12/-1
Fixed stuck voice waveform highlight after leaving hovered message.
telegramdesktop/tdesktop/dev • 8b4ccb7 • 1 files, +4/-0
Added display of hovered waveform time to voice message status line.
telegramdesktop/tdesktop/dev • 91fb168 • 2 files, +19/-17
Moved out IsDarkTaskbar() from the Windows tray module.
telegramdesktop/tdesktop/dev • 6477fdd • 6 files, +337/-5
Added playback controls to Windows taskbar thumbnail.
Fixed #30402.
telegramdesktop/tdesktop/dev • 1c6220a • 1 files, +1/-1
Update lib_ui submodule.
#tdesktop
telegramdesktop/tdesktop/dev • 2428069 • 2 files, +26/-14
[ai] Clean up implementing.md every run.
#tdesktop
telegramdesktop/tdesktop/dev • 5ba77ac • 2 files, +125/-21
[img-editor] Added pinch zoom gesture support.
telegramdesktop/tdesktop/dev • c9c02ad • 4 files, +36/-29
[poll-view] Fixed description being selected instead of question.
telegramdesktop/tdesktop/dev • c02a358 • 1 files, +1/-1
Fixed wrong caption being selected in file albums.
telegramdesktop/tdesktop/dev • e37fc1d • 2 files, +42/-0
Added pinch zoom gesture support to media viewer.
telegramdesktop/tdesktop/dev • 6ab3a8e • 4 files, +69/-5
Added swipe left-right navigation gesture to media viewer.
telegramdesktop/tdesktop/dev • 1a0b504 • 1 files, +3/-1
Added billions suffix to shortened counters.
telegramdesktop/tdesktop/dev • b023fd2 • 2 files, +11/-2
Limited contacts alphabetical section headers to contacts box.
telegramdesktop/tdesktop/dev • a2f2db3 • 3 files, +6/-2
Made discussion group open in same non-primary window as its channel.
telegramdesktop/tdesktop/dev • 57fdc4b • 2 files, +5/-1
Fixed macOS now playing timeline drift after pausing playback.
telegramdesktop/tdesktop/dev • 858a8fd • 2 files, +23/-1
Reflected the playback speed in macOS now playing timeline.
telegramdesktop/tdesktop/dev • 3adc0c1 • 2 files, +20/-1
Mapped macOS now playing skip buttons to relative seeking.
telegramdesktop/tdesktop/dev • b7dff42 • 2 files, +45/-14
[ai] Localize testing attempts.
telegramdesktop/tdesktop/dev • 4e6295b • 1 files, +154/-94
[ai] Bring Codex skill up to speed.
telegramdesktop/tdesktop/dev • 0782b4d • 2 files, +71/-19
[ai] Default to .ai/{project}/tasks/about.md task.
#tdesktop
telegramdesktop/tdesktop/nightly • 548185f • 2 files, +19/-17
Moved out IsDarkTaskbar() from the Windows tray module.
telegramdesktop/tdesktop/nightly • 14efa2d • 7 files, +342/-5
Added playback controls to Windows taskbar thumbnail.
#tdesktop
telegramdesktop/tdesktop/dev • 9f61a9f • 2 files, +4/-2
Work around freeze on Windows on ARM.
Fixes #30867.
#tdesktop
Endi mavjud! Telegram Tadqiqoti 2025 — yilning asosiy insaytlari 
