Skip to content

Telegram announcements split long sections; nested lists render on Telegram and XDA - #42

Merged
denson9874 merged 6 commits into
codex/pixel-parityfrom
claude/project-thread-ojbksd
Oct 30, 2026
Merged

denson9874 merged 6 commits into
codex/pixel-parityfrom
claude/project-thread-ojbksd

Conversation

@denson9874

@denson9874 denson9874 commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Description

Follow-up to #41 for the published 5.0.8 (70). No app code changes, so the sealed APKs are unaffected.

The 5.0.8 announcement's "What's New" block is 5,767 characters with no divider inside it. chunk_telegram_html only split at dividers, so a bot post would have sent the intro and then had message 2 refused for being over Telegram's 4,096-character limit, leaving half an announcement. The renders also had list problems on both Telegram and XDA.

scripts/post_telegram.py

  • Messages end at the last divider that fits, else the last blank line, line break or space, never inside a tag or entity. Length is the visible text in UTF-16 units, as Telegram counts it. A tag still open at a split (a quote) is closed there and reopened in the next message. A run of dividers is one break, a piece with no text of its own joins its neighbour, a break right after a heading is the last resort (also after a blank line or an emoji prefix), and a line break before a top-level line beats one inside a nested list.
  • The whole post's markup, then every message, is checked before the first one is sent (length, supported tags, nesting, non-empty). A refusal exits 4 (EXIT_NOT_POSTABLE) with nothing sent, and release_unified.py reports it as such. A dry run prints the message count and sizes.
  • Text is escaped, so a stray <, > or & can't break Telegram's HTML parse; the notes' own tags are kept, and entities they spell out show as their characters (except inside code). Entities are held as placeholders while Markdown is converted, so &gt; is never read as a quote and an escaped * or - stays literal. <br> becomes a newline.
  • Indented bullets render as nested ◦ items, and a list item's indented paragraph stays under its bullet.
  • Non-web links (obtainium://) show their address as code instead of raw Markdown.
  • --desktop-parts DIR writes each message as Telegram Desktop markdown (**bold**, __italic__, ||spoiler||, ~~strike~~, `code`) to DIR/part-N.txt. The Mac has no bot token, and announcements are posted by hand in Telegram Desktop, which doesn't read the HTML. Only the outermost of nested markers is written, since Telegram Desktop applies only that one. Parts are measured after link addresses are written out and re-split until each fits, and markers that won't format are reported (an allow-list of the separators Telegram Desktop accepts; code spans are skipped).

scripts/format_xda.py: list nesting follows each item's content column, so tabs and uneven indents nest correctly; an indented paragraph stays inside its list item, after any sub-list it follows, and a wrapped line continues its item.

docs/release_notes/5.0.8_announcement.md: "the attached APK" becomes "this build", since Telegram and XDA link to the APK rather than attach it. A Download line links qa-v5.0.8-70. Validation now carries the Pixel 7 Pro result for 5.0.8 (built into the ROM as the recents provider: previews, card actions, Clear all and Screenshot worked with no crashes; Overview's Share and Lens weren't on that phone to test), and section 1 says Share and Lens are checked in code only.

TELEGRAM_CHANGELOG.txt, docs/release_notes/xda_thread_post_5.0.8.bbcode: generated from the final notes. The BBCode is the one posted on XDA (post #40) with Daryl's approval; both GitHub release bodies were edited to the same text.

Testing

  • python3 -m unittest discover -s ci/tests: 406 tests OK (1 skipped).
  • New tests render every announcement in docs/release_notes (31 files): each splits into messages Telegram accepts with no words lost, and each XDA render has balanced lists and no indented lines. Others cover the split rules, tag balancing, the refusal checks and exit codes (with and without a token, and in a dry run), escaping and entities, nested bullets and item paragraphs, non-web links, a long divider run (linear time), and the Telegram Desktop parts (outermost markers, link addresses, warnings, fitting after links are expanded).
  • Two adversarial review rounds (fuzzing, Telegram Bot API docs, the tdesktop/lib_ui markdown source, old vs new renders of all 31 notes); all findings fixed. A random fuzz of about 7,700 more split runs after round 2 lost no text and produced no message Telegram would refuse or part over 4,096.
  • 5.0.8 splits into 4 bot messages of 648, 3,332, 2,994 and 3,448 characters (Desktop parts 648, 3,332, 2,994 and 3,524). Old vs new XDA renders differ only in nesting, and 5.0.4's matches the hand-fixed BBCode that was committed.
  • check-style fails on the base branch too (Spotless on 66 Kotlin files this PR doesn't touch); see the earlier comment.

Type of change

✅ Bug fix (A non-breaking change that fixes an issue)
❌ New feature (A non-breaking change that adds functionality)
❌ Breaking change (A fix or feature that would cause existing functionality to not work as expected)
❌ Refactor (A code change that neither fixes a bug nor adds a feature)
❌ Performance (A code change that improves performance)
❌ Style (Code style changes)
✅ Docs (Changes to documentation)
✅ Chore (Changes to the build process or other tooling)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Csz3takA5by6WEejggRjcP

The 5.0.8 announcement's "What's New" section is 5,767 characters with no
divider inside it. chunk_telegram_html only split at dividers, so the bot
would have posted the intro and then had the second message refused for
being over Telegram's 4,096-character limit, leaving half an announcement.

post_telegram.py:
- Messages end at the last divider that fits, else the last blank line,
  line break or space, never inside a tag or entity. Length is visible text
  in UTF-16 units, as Telegram counts it. A tag open at a split (a quote) is
  closed there and reopened in the next message.
- Every message is checked before the first is sent: length, tags Telegram
  supports, nesting. A failure exits 1 with nothing sent; a dry run reports
  the message count and sizes.
- Text is escaped, so a stray <, > or & can't break the HTML parse; the
  notes' own tags are kept. <br> becomes a newline.
- Indented bullets render as nested "◦" items, and an item's indented second
  paragraph loses its stray indent.
- Non-web links (obtainium://) show their address as code instead of raw
  Markdown.

format_xda.py: indented bullets open a nested [LIST], and an indented
paragraph stays inside its list item instead of closing and reopening the
list.

5.0.8 notes: "the attached APK" becomes "this build", since Telegram and XDA
link to the APK rather than attach it.

Tests cover every announcement in docs/release_notes (each splits into
messages Telegram accepts with no words lost; every XDA render has balanced
lists), plus splitting, tag balancing, escaping and the refusal checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Csz3takA5by6WEejggRjcP
There is no bot token on the release Mac, and announcements are posted by
hand in Telegram Desktop, which reads its own markdown (**bold**,
__italic__, ||spoiler||, `code`) rather than the HTML render.

post_telegram.py --desktop-parts DIR writes each message the chunker makes
as DIR/part-N.txt in that markdown, so each part stays under Telegram's
limit and keeps its formatting. Links show their address unless Telegram
links the label itself (a bare address or an @name). Stale parts are
removed first.

The 5.0.8 notes gain a Download line linking the release, since Telegram
and XDA readers otherwise only get the repository link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Csz3takA5by6WEejggRjcP

Copy link
Copy Markdown
Owner Author

check-style fails here for the same reason as on #41 and the base branch: ./gradlew spotlessKotlinCheck reports ktlint violations in 66 Kotlin files under lawnchair/src (e.g. ExpressiveCore.kt, FeedBridge.kt, AllAppsSearchInput.kt). This PR changes only Python scripts, their tests and a release-notes Markdown file, so none of those files is touched. No fix exists yet; it needs a repo-wide spotlessApply in its own PR so it doesn't bury this diff. The Python contract suite (python3 -m unittest discover -s ci/tests) passes: 391 tests, 1 skipped.


Generated by Claude Code

Telegram Desktop applies only the outermost of nested markers, so the
5.0.8 riddle answer, <tg-spoiler><b>…</b>…</tg-spoiler>, would have been
sent as ||**An app's Overview preview!**…|| with the asterisks showing.

post_telegram.py:
- Desktop parts write only the outermost marker, map strikethrough to ~~
  and pre to ```, and unescape a link address once. They are measured
  after links are written out and re-split with a smaller budget until
  each part fits. Text that holds **, __, ||, ~~ or a backtick, and markers
  next to a letter, digit or dash (which Telegram Desktop won't format),
  are reported as warnings.
- The whole post's markup is checked before splitting, since a split
  closes any tag left open. An empty post, or a message with no text, is
  refused. Refusals exit 4 (EXIT_NOT_POSTABLE) and release_unified.py
  reports "Telegram would refuse the rendered notes, nothing was sent"
  instead of asking to check for partial posts.
- A run of dividers is one break, and a piece with no text of its own (a
  divider, or a tag cut off at a split) joins its neighbour. A break right
  after a heading line is the last resort, and a line break before a
  top-level line beats one inside a nested list.
- Entities the notes spell out (&amp;, &#8212;) show as their characters,
  except inside `code`, where GitHub shows them as typed.

format_xda.py: an indented paragraph after a sub-list closes it and joins
the parent item (5.0.4's hand-fixed BBCode now comes out as committed);
tabs count as 4 columns; a bullet outdented between two levels stays
beside the deeper one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Csz3takA5by6WEejggRjcP
- Telegram: an indented paragraph inside a list stays under its bullet
  instead of becoming a separate line at the left edge, and sub-bullets
  render as indented hollow bullets.
- Telegram: HTML entities in the notes are held as placeholders while
  markdown is converted, so `&gt; ` is never read as a quote and an
  escaped `*` or `-` stays literal.
- Telegram: the divider-run pattern is linear, a heading counts as a
  break point after a blank line or an emoji prefix, and Desktop marker
  warnings use an allow-list of separators and skip code spans.
- XDA: list nesting follows the item's content column, so tabs and
  uneven indents nest correctly and a wrapped line continues its item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Csz3takA5by6WEejggRjcP
5.0.8 has now run on the Pixel 7 Pro as the recents provider: previews,
card actions, Clear all and Screenshot worked with no crashes. Say so,
say that Overview's own Share and Lens buttons haven't been tried on a
phone, and point reports at setups where Expressive runs Overview.
Regenerate the Telegram changelog and the XDA thread BBCode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Csz3takA5by6WEejggRjcP
@denson9874
denson9874 marked this pull request as ready for review October 30, 2026 22:29
@denson9874
denson9874 merged commit b7e190e into codex/pixel-parity Oct 30, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants