Ajaxy/telegram-tt/master • f45c51b • 3 files, +4/-4
12.0.30
Ajaxy/telegram-tt/master • cf4b734 • 1 files, +2/-2
Chat Extra: Fix unofficial security risk block (#6977)
Ajaxy/telegram-tt/master • 0f2a87c • 1 files, +5/-2
Poll: Disable sending quiz with 1 option (#6980)
Ajaxy/telegram-tt/master • 7ddb46e • 3 files, +20/-1
Bot Forum: Change botoforum info button string (#6979)
Ajaxy/telegram-tt/master • 33ef4fb • 1 files, +14/-2
Chat List: Fix messege appearance after chat deletion (#6978)
Ajaxy/telegram-tt/master • 98e7644 • 115 files, +1925/-5812
Migrate to Vite (#6973)
Ajaxy/telegram-tt/master • 6aa405f • 6 files, +1654/-156
[CI] Bundle Stats: Add Vite PR reporting (#6976)
Ajaxy/telegram-tt/master • f50eefa • 7 files, +4305/-6728
Tests: Migrate Jest suite to Vitest (#6981)
Ajaxy/telegram-tt/master • bac8e43 • 2 files, +73/-58
PR Stats: Change format (#6983)
Ajaxy/telegram-tt/master • af5008b • 26 files, +544/-64
Reaction: Support reaction deletion (#6982)
Ajaxy/telegram-tt/master • 754be91 • 4 files, +90/-23
Vite: Show workers in stats (#7010)
Ajaxy/telegram-tt/master • 194f199 • 60 files, +836/-745
Remove Buffer dependency (#7009)
Ajaxy/telegram-tt/master • 49f82ed • 1 files, +1/-0
Sync: Fix outdated global error (#7014)
Ajaxy/telegram-tt/master • 38e4b77 • 5 files, +15/-29
Safari: Fix several problems (#7015)
Ajaxy/telegram-tt/master • 9f01ca8 • 11 files, +210/-195
GifButton: Move to SCSS Modules (#7013)
#webz
12.0.30
Ajaxy/telegram-tt/master • cf4b734 • 1 files, +2/-2
Chat Extra: Fix unofficial security risk block (#6977)
Ajaxy/telegram-tt/master • 0f2a87c • 1 files, +5/-2
Poll: Disable sending quiz with 1 option (#6980)
Ajaxy/telegram-tt/master • 7ddb46e • 3 files, +20/-1
Bot Forum: Change botoforum info button string (#6979)
Ajaxy/telegram-tt/master • 33ef4fb • 1 files, +14/-2
Chat List: Fix messege appearance after chat deletion (#6978)
Ajaxy/telegram-tt/master • 98e7644 • 115 files, +1925/-5812
Migrate to Vite (#6973)
Ajaxy/telegram-tt/master • 6aa405f • 6 files, +1654/-156
[CI] Bundle Stats: Add Vite PR reporting (#6976)
Ajaxy/telegram-tt/master • f50eefa • 7 files, +4305/-6728
Tests: Migrate Jest suite to Vitest (#6981)
Ajaxy/telegram-tt/master • bac8e43 • 2 files, +73/-58
PR Stats: Change format (#6983)
Ajaxy/telegram-tt/master • af5008b • 26 files, +544/-64
Reaction: Support reaction deletion (#6982)
Ajaxy/telegram-tt/master • 754be91 • 4 files, +90/-23
Vite: Show workers in stats (#7010)
Ajaxy/telegram-tt/master • 194f199 • 60 files, +836/-745
Remove Buffer dependency (#7009)
Ajaxy/telegram-tt/master • 49f82ed • 1 files, +1/-0
Sync: Fix outdated global error (#7014)
Ajaxy/telegram-tt/master • 38e4b77 • 5 files, +15/-29
Safari: Fix several problems (#7015)
Ajaxy/telegram-tt/master • 9f01ca8 • 11 files, +210/-195
GifButton: Move to SCSS Modules (#7013)
#webz
🫡2
Ajaxy/telegram-tt/master • 48bf95b • 1 files, +30/-15
Forum: Load topics when listed ids are missing (#7016)
Ajaxy/telegram-tt/master • bee4c3a • 1 files, +2/-2
Left Main: Fix chat list with tabs layout (#7017)
Ajaxy/telegram-tt/master • 34474e7 • 6 files, +54/-8
Profile: Implement round corners in profile media (#7019)
Ajaxy/telegram-tt/master • f23a4ba • 3 files, +594/-597
Agents: Move CLAUDE.md to AGENTS.md (#7012)
Ajaxy/telegram-tt/master • d419be9 • 11 files, +31/-43
Sticker: Fix blinking on send (#7011)
Ajaxy/telegram-tt/master • 0b47bec • 2 files, +2/-2
Audio: Fix audio message transcribing button color (#7021)
Ajaxy/telegram-tt/master • b52deb1 • 3 files, +169/-12
Checklists: Fix emoji display for checklists in chat list (#7018)
Ajaxy/telegram-tt/master • 173280d • 4 files, +14/-6
Summary: Prevent hiding on error (#7023)
Ajaxy/telegram-tt/master • 764aaa4 • 2 files, +0/-508
Remove unused dependencies (#7024)
Ajaxy/telegram-tt/master • ccd4bdc • 11 files, +0/-88
Remove unused functions (#7025)
Ajaxy/telegram-tt/master • 9c285e7 • 41 files, +156/-4438
Statistics: Upgrade to lovely-chart v2 npm package (#7027)
Ajaxy/telegram-tt/master • 54185fd • 7 files, +168/-139
Statistics: Restore lazy lovely-chart loading (#7028)
Ajaxy/telegram-tt/master • 954f0f1 • 121 files, +7622/-759
Support Rich Messages & Instant View (#6963)
Ajaxy/telegram-tt/master • 0df85b8 • 4 files, +14/-13
Lang: Updates from platform (#7030)
#webz
Forum: Load topics when listed ids are missing (#7016)
Ajaxy/telegram-tt/master • bee4c3a • 1 files, +2/-2
Left Main: Fix chat list with tabs layout (#7017)
Ajaxy/telegram-tt/master • 34474e7 • 6 files, +54/-8
Profile: Implement round corners in profile media (#7019)
Ajaxy/telegram-tt/master • f23a4ba • 3 files, +594/-597
Agents: Move CLAUDE.md to AGENTS.md (#7012)
Ajaxy/telegram-tt/master • d419be9 • 11 files, +31/-43
Sticker: Fix blinking on send (#7011)
Ajaxy/telegram-tt/master • 0b47bec • 2 files, +2/-2
Audio: Fix audio message transcribing button color (#7021)
Ajaxy/telegram-tt/master • b52deb1 • 3 files, +169/-12
Checklists: Fix emoji display for checklists in chat list (#7018)
Ajaxy/telegram-tt/master • 173280d • 4 files, +14/-6
Summary: Prevent hiding on error (#7023)
Ajaxy/telegram-tt/master • 764aaa4 • 2 files, +0/-508
Remove unused dependencies (#7024)
Ajaxy/telegram-tt/master • ccd4bdc • 11 files, +0/-88
Remove unused functions (#7025)
Ajaxy/telegram-tt/master • 9c285e7 • 41 files, +156/-4438
Statistics: Upgrade to lovely-chart v2 npm package (#7027)
Ajaxy/telegram-tt/master • 54185fd • 7 files, +168/-139
Statistics: Restore lazy lovely-chart loading (#7028)
Ajaxy/telegram-tt/master • 954f0f1 • 121 files, +7622/-759
Support Rich Messages & Instant View (#6963)
Ajaxy/telegram-tt/master • 0df85b8 • 4 files, +14/-13
Lang: Updates from platform (#7030)
#webz
🫡2
Ajaxy/telegram-tt/master • 68478f6 • 45 files, +615/-401
Redesign: Redesign Left panel and Middle header (#6971)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
Ajaxy/telegram-tt/master • e1f2988 • 1 files, +1/-1
Add .kml / .kmz as potentially IP-revealing. (#7031)
Ajaxy/telegram-tt/master • 73ff150 • 10 files, +76/-63
Rich Messages: Small fixes (#7032)
Ajaxy/telegram-tt/master • 1fafd66 • 1 files, +2/-2
Reactor List Modal: Fix button hover (#7034)
Ajaxy/telegram-tt/master • 1ead092 • 1 files, +2/-1
Topic: Fix error on min constructor (#7022)
Ajaxy/telegram-tt/master • ee72f71 • 10 files, +47/-33
Media Viewer: Fix ghost animation clipping (#7037)
Ajaxy/telegram-tt/master • 4cd5e76 • 8 files, +368/-6
Profile: Show long titles in line (#7020)
Ajaxy/telegram-tt/master • 3d85e97 • 12 files, +96/-99
Middle Column: Support mask gradient (#7036)
Ajaxy/telegram-tt/master • 54f4693 • 27 files, +153/-74
Toncoin: Rebrand token to Gram
Rename the Toncoin token to "Gram" across user-facing strings and
localization keys, and add the new colored coin icon. The TON blockchain,
the `TON` currency code and protocol/API entities are left intact.
- Update display strings to use the name "Gram"
- Rename token localization keys (`*Ton*` -> `*Gram*`)
- Add `GramIcon` (colored SVG inline, PNG in Settings, `currentColor`
monochrome variant for amount-colored transactions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajaxy/telegram-tt/master • 6f02c7f • 1 files, +2/-1
Rich Message: Fix more button hover color (#7038)
Ajaxy/telegram-tt/master • bada5e5 • 2 files, +20/-0
Modal: Fix Esc handling (#7043)
Ajaxy/telegram-tt/master • b9a888f • 18 files, +452/-493
Mini Apps: Fix theme color parsing (#7029)
Ajaxy/telegram-tt/master • b7764bc • 17 files, +51/-30
Search: Fix search input position in chat list (#7040)
#webz
Redesign: Redesign Left panel and Middle header (#6971)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
Ajaxy/telegram-tt/master • e1f2988 • 1 files, +1/-1
Add .kml / .kmz as potentially IP-revealing. (#7031)
Ajaxy/telegram-tt/master • 73ff150 • 10 files, +76/-63
Rich Messages: Small fixes (#7032)
Ajaxy/telegram-tt/master • 1fafd66 • 1 files, +2/-2
Reactor List Modal: Fix button hover (#7034)
Ajaxy/telegram-tt/master • 1ead092 • 1 files, +2/-1
Topic: Fix error on min constructor (#7022)
Ajaxy/telegram-tt/master • ee72f71 • 10 files, +47/-33
Media Viewer: Fix ghost animation clipping (#7037)
Ajaxy/telegram-tt/master • 4cd5e76 • 8 files, +368/-6
Profile: Show long titles in line (#7020)
Ajaxy/telegram-tt/master • 3d85e97 • 12 files, +96/-99
Middle Column: Support mask gradient (#7036)
Ajaxy/telegram-tt/master • 54f4693 • 27 files, +153/-74
Toncoin: Rebrand token to Gram
Rename the Toncoin token to "Gram" across user-facing strings and
localization keys, and add the new colored coin icon. The TON blockchain,
the `TON` currency code and protocol/API entities are left intact.
- Update display strings to use the name "Gram"
- Rename token localization keys (`*Ton*` -> `*Gram*`)
- Add `GramIcon` (colored SVG inline, PNG in Settings, `currentColor`
monochrome variant for amount-colored transactions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajaxy/telegram-tt/master • 6f02c7f • 1 files, +2/-1
Rich Message: Fix more button hover color (#7038)
Ajaxy/telegram-tt/master • bada5e5 • 2 files, +20/-0
Modal: Fix Esc handling (#7043)
Ajaxy/telegram-tt/master • b9a888f • 18 files, +452/-493
Mini Apps: Fix theme color parsing (#7029)
Ajaxy/telegram-tt/master • b7764bc • 17 files, +51/-30
Search: Fix search input position in chat list (#7040)
#webz
🫡2
Ajaxy/telegram-tt/master • 5a78bb0 • 7 files, +275/-76
Calls: Migrate methods to object parameters (#7044)
Ajaxy/telegram-tt/master • 248690e • 1 files, +3/-0
Inline Bots: Support rich content (#7046)
Ajaxy/telegram-tt/master • 7b64d1b • 7 files, +15/-8
Styles: Redesign shadows (#7039)
Ajaxy/telegram-tt/master • 37480bc • 1 files, +1/-1
Folders: Disable left tab folders grey out in inactive state (#7041)
Ajaxy/telegram-tt/master • 7a618df • 2 files, +4/-2
Chat Invite Modal: Redesign (#7047)
Ajaxy/telegram-tt/master • 9602b82 • 11 files, +96/-89
Revert "Middle Column: Support mask gradient (#7036)" (#7049)
Ajaxy/telegram-tt/master • 110eb87 • 1 files, +1/-1
Files: Fix progress spinner height init (#7052)
Ajaxy/telegram-tt/master • bcdf2a4 • 2 files, +1/-5
Contact Modal: Fix top margin for help text in New Contact Modal (#7053)
Ajaxy/telegram-tt/master • 84ee447 • 9 files, +68/-6
Message: Show edited if needed (#7045)
Ajaxy/telegram-tt/master • 3f795db • 31 files, +934/-157
Management: Support guard bots (#7035)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
Ajaxy/telegram-tt/master • 0b401d4 • 1 files, +4/-0
Quick Preview Modal: Fix layout (#7054)
Ajaxy/telegram-tt/master • fc777e6 • 300 files, +21565/-1530
[Build]
#webz
Calls: Migrate methods to object parameters (#7044)
Ajaxy/telegram-tt/master • 248690e • 1 files, +3/-0
Inline Bots: Support rich content (#7046)
Ajaxy/telegram-tt/master • 7b64d1b • 7 files, +15/-8
Styles: Redesign shadows (#7039)
Ajaxy/telegram-tt/master • 37480bc • 1 files, +1/-1
Folders: Disable left tab folders grey out in inactive state (#7041)
Ajaxy/telegram-tt/master • 7a618df • 2 files, +4/-2
Chat Invite Modal: Redesign (#7047)
Ajaxy/telegram-tt/master • 9602b82 • 11 files, +96/-89
Revert "Middle Column: Support mask gradient (#7036)" (#7049)
Ajaxy/telegram-tt/master • 110eb87 • 1 files, +1/-1
Files: Fix progress spinner height init (#7052)
Ajaxy/telegram-tt/master • bcdf2a4 • 2 files, +1/-5
Contact Modal: Fix top margin for help text in New Contact Modal (#7053)
Ajaxy/telegram-tt/master • 84ee447 • 9 files, +68/-6
Message: Show edited if needed (#7045)
Ajaxy/telegram-tt/master • 3f795db • 31 files, +934/-157
Management: Support guard bots (#7035)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
Ajaxy/telegram-tt/master • 0b401d4 • 1 files, +4/-0
Quick Preview Modal: Fix layout (#7054)
Ajaxy/telegram-tt/master • fc777e6 • 300 files, +21565/-1530
[Build]
#webz
🫡2
morethanwords/tweb/master • 11fde94 • 6 files, +38/-13
Misc non-sticker fixes: folders sidebar notification badge, media spoiler cover, reactions no-select, CLAUDE.md note, langPack sync
- folders sidebar: reactive total notification count on the menu button (Accessor instead of cloning the toolbar count element)
- media: keep sensitive-content cover clickable during reveal so a repeat click is ignored instead of falling through
- reactions: disable text selection
- CLAUDE.md: add commit-policy note
- regenerate langPack.strings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#webk
Misc non-sticker fixes: folders sidebar notification badge, media spoiler cover, reactions no-select, CLAUDE.md note, langPack sync
- folders sidebar: reactive total notification count on the menu button (Accessor instead of cloning the toolbar count element)
- media: keep sensitive-content cover clickable during reveal so a repeat click is ignored instead of falling through
- reactions: disable text selection
- CLAUDE.md: add commit-policy note
- regenerate langPack.strings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#webk
🫡3
morethanwords/tweb/master • 550b488 • 3 files, +195/-68
fix: format a shared contact's phone under the viewer's country, not a misread foreign code
Bug (bugs.telegram.org #30681): a phone number in an in-chat shared contact
embed is displayed with the wrong country code when the stored vCard number does
not explicitly contain its dialing prefix. The leading national/city digits are
shown as if they were the country code (does not happen on Android).
Root cause: formatPhoneNumber (src/helpers/formatPhoneNumber.ts) strips every
non-digit and then ALWAYS resolves a country by greedily matching the longest
numeric prefix against every known country code. For a number stored without its
dialing prefix (e.g. an Indonesian national mobile "8123456789"), the leading
"81" collide with Japan (+81): "81" is stripped as the country code and the rest
is regrouped under it, so "8123456789" rendered as "+81 23 4567 89" in the
contact bubble (src/components/chat/bubbles.ts, messageMediaContact). Registered
users (user.phone) always carry their country code, so this only surfaces for the
arbitrary vCard phone_number of a shared contact.
Fix: mirror Android's PhoneFormat, which biases an unprefixed number to the
viewer's own country instead of guessing one from the leading digits.
- formatPhoneNumber takes an opt-in `defaultCountryCode` (the viewer's own
calling code). When the input has no explicit '+' AND its digits do not already
start with that code, the number is treated as a national number of the
viewer's country and grouped with that country's national patterns; `code` is
returned undefined so the caller omits the '+'. Otherwise (explicit '+', or
digits already starting with the viewer's code) the existing greedy detection
runs unchanged, so a registered user's fully-qualified phone and an
international "+..." contact keep their grouping. The grouping logic is factored
into a shared applyPattern() helper.
- bubbles.ts reads the viewer's country code once from the warm main-thread user
cache (apiManagerProxy.getUser(rootScope.myId)) — synchronous, no chat-open
round-trip — and prefixes '+' only when a country code is present. Every other
formatPhoneNumber caller (profile, search, payments, telInputField) is
unchanged.
Result: a same-country contact regains correct grouping (e.g. "+7 999 123-45-67"),
the #30681 number is grouped under the viewer's country with no false foreign code
(Indonesian "8123456789" -> "812 3456789", never "+81 ..."), and a number that
does carry its own code still renders "+CC ...".
Test (src/tests/formatPhoneNumber.test.ts): pure-unit Vitest with two countries
whose leading digits overlap (JP +81, ID +62). Asserts the #30681 input is
grouped under the viewer (+62) with code undefined and never attributed to Japan;
plus the own-country international "+CC", explicit-'+' detection, the
foreign-under-viewer no-"+CC" case, and the unchanged default parse. 6/6 green
(one reproduces the bug before the fix).
Gates: tsc --noEmit 0 errors; eslint 0 on all changed files; vitest 6/6 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
fix: format a shared contact's phone under the viewer's country, not a misread foreign code
Bug (bugs.telegram.org #30681): a phone number in an in-chat shared contact
embed is displayed with the wrong country code when the stored vCard number does
not explicitly contain its dialing prefix. The leading national/city digits are
shown as if they were the country code (does not happen on Android).
Root cause: formatPhoneNumber (src/helpers/formatPhoneNumber.ts) strips every
non-digit and then ALWAYS resolves a country by greedily matching the longest
numeric prefix against every known country code. For a number stored without its
dialing prefix (e.g. an Indonesian national mobile "8123456789"), the leading
"81" collide with Japan (+81): "81" is stripped as the country code and the rest
is regrouped under it, so "8123456789" rendered as "+81 23 4567 89" in the
contact bubble (src/components/chat/bubbles.ts, messageMediaContact). Registered
users (user.phone) always carry their country code, so this only surfaces for the
arbitrary vCard phone_number of a shared contact.
Fix: mirror Android's PhoneFormat, which biases an unprefixed number to the
viewer's own country instead of guessing one from the leading digits.
- formatPhoneNumber takes an opt-in `defaultCountryCode` (the viewer's own
calling code). When the input has no explicit '+' AND its digits do not already
start with that code, the number is treated as a national number of the
viewer's country and grouped with that country's national patterns; `code` is
returned undefined so the caller omits the '+'. Otherwise (explicit '+', or
digits already starting with the viewer's code) the existing greedy detection
runs unchanged, so a registered user's fully-qualified phone and an
international "+..." contact keep their grouping. The grouping logic is factored
into a shared applyPattern() helper.
- bubbles.ts reads the viewer's country code once from the warm main-thread user
cache (apiManagerProxy.getUser(rootScope.myId)) — synchronous, no chat-open
round-trip — and prefixes '+' only when a country code is present. Every other
formatPhoneNumber caller (profile, search, payments, telInputField) is
unchanged.
Result: a same-country contact regains correct grouping (e.g. "+7 999 123-45-67"),
the #30681 number is grouped under the viewer's country with no false foreign code
(Indonesian "8123456789" -> "812 3456789", never "+81 ..."), and a number that
does carry its own code still renders "+CC ...".
Test (src/tests/formatPhoneNumber.test.ts): pure-unit Vitest with two countries
whose leading digits overlap (JP +81, ID +62). Asserts the #30681 input is
grouped under the viewer (+62) with code undefined and never attributed to Japan;
plus the own-country international "+CC", explicit-'+' detection, the
foreign-under-viewer no-"+CC" case, and the unchanged default parse. 6/6 green
(one reproduces the bug before the fix).
Gates: tsc --noEmit 0 errors; eslint 0 on all changed files; vitest 6/6 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#webk
🫡3
telegramdesktop/tdesktop/dev • ef76b58 • 6 files, +45/-29
Fix reaction preview crash in stories.
telegramdesktop/tdesktop/dev • 2622f02 • 5 files, +487/-98
Improve context of messages copied to clipboard.
#tdesktop
Fix reaction preview crash in stories.
telegramdesktop/tdesktop/dev • 2622f02 • 5 files, +487/-98
Improve context of messages copied to clipboard.
#tdesktop
🫡3
telegramdesktop/tdesktop/dev • 9f61a9f • 2 files, +4/-2
Work around freeze on Windows on ARM.
Fixes #30867.
#tdesktop
Work around freeze on Windows on ARM.
Fixes #30867.
#tdesktop
🫡3
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
Moved out IsDarkTaskbar() from the Windows tray module.
telegramdesktop/tdesktop/nightly • 14efa2d • 7 files, +342/-5
Added playback controls to Windows taskbar thumbnail.
#tdesktop
🫡3
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
[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
🫡3
telegramdesktop/tdesktop/dev • 2428069 • 2 files, +26/-14
[ai] Clean up implementing.md every run.
#tdesktop
[ai] Clean up implementing.md every run.
#tdesktop
🫡2
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
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
🫡2
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
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
🫡2
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
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
🫡3
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
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
🫡3
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
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
🫡3
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
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
🫡3
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
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
🫡3
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
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
🫡3
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
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
🫡3