uz
Feedback
Telegram github commits and releases

Telegram github commits and releases

Kanalga Telegram’da o‘tish

Broadcast from the most important Telegram clients' repositories

Ko'proq ko'rsatish
4 822
Obunachilar
-1124 soatlar
-297 kunlar
-10930 kunlar
Postlar arxiv
telegramdesktop/tdesktop/dev228934b1 files, +4/-1 Work around qmake race condition #tdesktop

TGX-Android/Telegram-X/main1e203012 files, +3/-1 Add `version.sdk_package` property #tgxandroid

TGX-Android/Telegram-X/maind76e7b56 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/mainf5f283a1 files, +1/-1 Version bump to `1788` #tgxandroid

TGX-Android/Telegram-X/main73ba0b742 files, +2354/-572 Upgrade TDLib to tdlib/td@e0943d0 #tgxandroid

morethanwords/tweb/masterca25dd13 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/master47e1ace1 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/master57fe5bc1 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/masterdde50002 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/master86bc5562 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/master66b6cc62 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/mastere5aab723 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/master387bfb72 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/master4d1a80c2 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/masteref495592 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/master8af53862 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/master3afbb332 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/masteraa433ff2 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/master15221e22 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/master154f42d2 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/deve99928b2 files, +25/-9 Added custom emoji size support to sticker uploading. telegramdesktop/tdesktop/dev6241d233 files, +137/-40 Added custom emoji creation flow to sticker creator box. telegramdesktop/tdesktop/dev103c2567 files, +157/-51 Added new-emoji button with menu to emoji set boxes. telegramdesktop/tdesktop/dev9895e5b4 files, +128/-0 Added adapting existing sticker into custom emoji. telegramdesktop/tdesktop/devff2dfbf5 files, +65/-21 Improve accessibility changes for left filters. telegramdesktop/tdesktop/deve4cde251 files, +1/-1 Fix build with Qt 5. #tdesktop

telegramdesktop/tdesktop/dev18df9412 files, +109/-1 Added screen reader tab-control accessibility for chat folders. telegramdesktop/tdesktop/dev7ffb2c51 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/devc48860d1 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/dev9a5ec8b3 files, +12/-1 Fixed stuck voice waveform highlight after leaving hovered message. telegramdesktop/tdesktop/dev8b4ccb71 files, +4/-0 Added display of hovered waveform time to voice message status line. telegramdesktop/tdesktop/dev91fb1682 files, +19/-17 Moved out IsDarkTaskbar() from the Windows tray module. telegramdesktop/tdesktop/dev6477fdd6 files, +337/-5 Added playback controls to Windows taskbar thumbnail. Fixed #30402. telegramdesktop/tdesktop/dev1c6220a1 files, +1/-1 Update lib_ui submodule. #tdesktop

telegramdesktop/tdesktop/dev24280692 files, +26/-14 [ai] Clean up implementing.md every run. #tdesktop

telegramdesktop/tdesktop/dev5ba77ac2 files, +125/-21 [img-editor] Added pinch zoom gesture support. telegramdesktop/tdesktop/devc9c02ad4 files, +36/-29 [poll-view] Fixed description being selected instead of question. telegramdesktop/tdesktop/devc02a3581 files, +1/-1 Fixed wrong caption being selected in file albums. telegramdesktop/tdesktop/deve37fc1d2 files, +42/-0 Added pinch zoom gesture support to media viewer. telegramdesktop/tdesktop/dev6ab3a8e4 files, +69/-5 Added swipe left-right navigation gesture to media viewer. telegramdesktop/tdesktop/dev1a0b5041 files, +3/-1 Added billions suffix to shortened counters. telegramdesktop/tdesktop/devb023fd22 files, +11/-2 Limited contacts alphabetical section headers to contacts box. telegramdesktop/tdesktop/deva2f2db33 files, +6/-2 Made discussion group open in same non-primary window as its channel. telegramdesktop/tdesktop/dev57fdc4b2 files, +5/-1 Fixed macOS now playing timeline drift after pausing playback. telegramdesktop/tdesktop/dev858a8fd2 files, +23/-1 Reflected the playback speed in macOS now playing timeline. telegramdesktop/tdesktop/dev3adc0c12 files, +20/-1 Mapped macOS now playing skip buttons to relative seeking. telegramdesktop/tdesktop/devb7dff422 files, +45/-14 [ai] Localize testing attempts. telegramdesktop/tdesktop/dev4e6295b1 files, +154/-94 [ai] Bring Codex skill up to speed. telegramdesktop/tdesktop/dev0782b4d2 files, +71/-19 [ai] Default to .ai/{project}/tasks/about.md task. #tdesktop

telegramdesktop/tdesktop/nightly548185f2 files, +19/-17 Moved out IsDarkTaskbar() from the Windows tray module. telegramdesktop/tdesktop/nightly14efa2d7 files, +342/-5 Added playback controls to Windows taskbar thumbnail. #tdesktop

telegramdesktop/tdesktop/dev9f61a9f2 files, +4/-2 Work around freeze on Windows on ARM. Fixes #30867. #tdesktop