Default teaser-thread to 3 posts with link-card embed on CTA#49
Default teaser-thread to 3 posts with link-card embed on CTA#49
Conversation
The teaser-thread strategy now produces a hook + body chunk + CTA reply by default, falling back to the previous hook + CTA pair when the body is too short for a chunk. The terminal CTA reply now carries an app.bsky.embed.external link card (title + excerpt + thumbnail), so direct visitors to a single post still have a clear path back to the WordPress article. Both changes target real-world UX gaps surfaced by FOSSE: 2-post threads get bundled/hidden on bsky.app's profile views, and a CTA without a card provides no rich affordance for the link. The atmosphere_teaser_thread_posts filter keeps its existing contract (2..5 string entries) and stays the universal escape hatch. The embed attaches to whichever entry is terminal so 2-entry filter overrides and the short-post fallback both still ship a CTA-with-card. Fixes Automattic/fosse#64
There was a problem hiding this comment.
Pull request overview
Updates the long-form teaser-thread strategy to reliably surface on bsky.app profiles by defaulting to a 3-post self-reply thread and adding a link-card embed to the terminal CTA, improving discoverability and click-through back to the original WordPress post.
Changes:
- Change default
teaser-threadoutput from 2 posts to 3 posts (hook, body chunk, CTA), with an automatic 2-post fallback for short bodies. - Attach an
app.bsky.embed.externallink card to the terminal CTA entry (last entry), including title/excerpt/thumbnail. - Update admin help text and PHPUnit expectations to reflect the new thread shape and embed behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
includes/transformer/class-post.php |
Implements 3-post default teaser thread, 2-post fallback, and terminal link-card embed support. |
includes/wp-admin/class-admin.php |
Updates settings UI help text describing the teaser-thread strategy. |
tests/phpunit/tests/transformer/class-test-post.php |
Expands/updates transformer tests for 3-post default, fallback behavior, and terminal embed placement. |
tests/phpunit/tests/class-test-publisher.php |
Adjusts publisher protocol fixtures to keep tests exercising 2-record write paths where intended. |
.github/changelog/teaser-thread-3post-card |
Adds changelog entry documenting the behavior change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sweep of multi-reviewer findings on the teaser-thread iteration:
* sanitize_text now strips NBSP (U+00A0) and ideographic space (U+3000)
too — the prior /\s+/ regex (no /u) left them intact, so an excerpt
of `\u{00A0}` × 10 would survive the 10-char floor in
has_composable_body() and a filter returning whitespace-only entries
would publish empty records.
* Body-chunk extraction in build_teaser_thread() ltrims with
/^\s+/u so a multibyte whitespace boundary in the body cannot leak
invisible leading whitespace into the chunk record.
* atmosphere_teaser_thread_posts now triggers _doing_it_wrong on a
non-array filter return (parity with the existing < 2 entries case),
so filter authors don't silently fall back to the default. Filter
docblock now spells out the post-filter pipeline (sanitize_text +
truncate + 5-cap) so authors don't think their strings ship verbatim.
* _doing_it_wrong message no longer claims a 3-entry fallback when the
default may be 2-entry; settings page help text mentions the body
chunk is optional.
New regression tests:
* sanitize_text Unicode-whitespace normalization.
* Hard-cap multibyte ellipsis path: chunk continues from the next
codepoint, not corrupted by a byte-level rtrim.
* Body chunk word-cut fallback when the chunk source has no sentence
punctuation.
* Filter cap of 5 entries (silent array_slice).
* Non-array filter return triggers _doing_it_wrong.
* Whitespace-only filter entries fall back after sanitisation.
There was a problem hiding this comment.
Pull request overview
Updates Atmosphere’s long-form teaser-thread composition to improve Bluesky UX by making threads reliably visible in profile feeds and adding a clear link-card affordance back to the WordPress post.
Changes:
- Default
teaser-threadoutput becomes 3 records (hook, body chunk, CTA), with a 2-record fallback for short bodies. - Adds an
app.bsky.embed.externallink-card embed to the terminal CTA entry (i.e., “last entry”). - Updates sanitization to normalize Unicode whitespace and refreshes admin help text + PHPUnit coverage accordingly.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
includes/transformer/class-post.php |
Implements 3-post teaser thread, body-chunk logic, and terminal link-card embed attachment. |
includes/functions.php |
Updates sanitize_text() whitespace normalization to be Unicode-aware. |
includes/wp-admin/class-admin.php |
Updates settings help text to reflect new teaser-thread behavior and link card. |
tests/phpunit/tests/transformer/class-test-post.php |
Expands/updates transformer tests for 3-post default, fallback behavior, filter behavior, and embed placement. |
tests/phpunit/tests/class-test-publisher.php |
Adjusts publisher protocol tests to keep exercising 2-record thread paths where intended. |
tests/phpunit/tests/class-test-functions.php |
Adds coverage for Unicode whitespace normalization in sanitize_text(). |
.github/changelog/teaser-thread-3post-card |
Adds changelog entry for the behavioral change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PCRE in `/u` mode validates the input as UTF-8 and returns null on malformed sequences (e.g. orphaned continuation bytes). PHP 8.1+ trim(null) and mb_strlen(null) raise TypeError, so on invalid input sanitize_text() and build_teaser_thread()'s leading-whitespace strip would fatal instead of degrading gracefully. Both call sites now check is_string on the preg_replace result and fall back to the pre-replacement value, mirroring how truncate_to_budget already handles its own /u regex. New test pins the sanitize_text behavior on a malformed sequence.
There was a problem hiding this comment.
Pull request overview
Updates Atmosphere’s long-form teaser-thread composition to improve Bluesky profile visibility and add a stronger “return to WordPress” affordance via a link-card on the terminal CTA.
Changes:
- Default
teaser-threadoutput becomes 3 posts (hook, body chunk, CTA) with an automatic 2-post fallback when the remaining body is too short. - Attach an
app.bsky.embed.externallink-card embed to the terminal CTA entry (works for both 2- and 3-entry defaults and filter overrides). - Improve text sanitization to normalize Unicode whitespace safely and expand PHPUnit coverage for the new thread behavior and sanitization edge cases.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
includes/transformer/class-post.php |
Implements 3-post default teaser-thread, 2-post fallback logic, and terminal CTA embed attachment. |
includes/functions.php |
Updates sanitize_text() to collapse Unicode whitespace using /u and safely handle malformed UTF-8. |
includes/wp-admin/class-admin.php |
Updates settings UI help text to reflect the new teaser-thread behavior and link card. |
tests/phpunit/tests/transformer/class-test-post.php |
Updates/extends transformer tests for 3-post default, fallback shape, embed attachment, and chunk extraction rules. |
tests/phpunit/tests/class-test-publisher.php |
Adjusts publisher protocol fixtures to keep 2-entry expectations where needed (via empty excerpt/body). |
tests/phpunit/tests/class-test-functions.php |
Adds tests for Unicode whitespace normalization and malformed UTF-8 handling in sanitize_text(). |
.github/changelog/teaser-thread-3post-card |
Adds changelog entry for the behavioral change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Updates the teaser-thread long-form composition strategy so Bluesky threads reliably surface on profiles and the CTA provides a stronger link affordance back to the original WordPress post.
Changes:
- Default
teaser-threadoutput is now 3 posts (hook, body chunk, CTA) with an automatic fallback to 2 posts (hook, CTA) for short bodies. - Adds an
app.bsky.embed.externallink-card embed to the terminal CTA entry (including when filters override the number of entries). - Improves text sanitization to normalize Unicode whitespace and safely handle invalid UTF-8 in
/uregex paths; updates and expands PHPUnit coverage accordingly.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
includes/transformer/class-post.php |
Implements 3-post teaser-thread default + 2-post fallback, attaches link-card embed to terminal entry, and extends record builder to accept an optional embed. |
includes/functions.php |
Makes sanitize_text() Unicode-whitespace aware and adds a safe fallback when preg_replace returns null. |
includes/wp-admin/class-admin.php |
Updates settings UI help text to reflect the new “short thread + link card” behavior. |
tests/phpunit/tests/transformer/class-test-post.php |
Updates existing expectations and adds extensive new tests for teaser-thread chunking, fallback behavior, filter contracts, and embed attachment. |
tests/phpunit/tests/class-test-publisher.php |
Adjusts publisher protocol tests to exercise the intended 2-entry fallback path (keeping protocol assertions stable). |
tests/phpunit/tests/class-test-functions.php |
Adds tests for Unicode whitespace normalization and invalid UTF-8 handling in sanitize_text(). |
.github/changelog/teaser-thread-3post-card |
Adds changelog entry documenting the behavior change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Fixes Automattic/fosse#64
Proposed changes:
Iterates on the unreleased
teaser-threadlong-form strategy to address two real-world UX problems FOSSE found in production:getAuthorFeed?filter=posts_no_repliesdrops the root,posts_with_repliesshows the reply but not the root). Threads live on the PDS but are unreachable via profile browsing./post/<rkey>direct link), there was no clear way for a reader to reach the original WordPress post.Two unconditional behavior changes:
build_teaser_thread()now returns[ hook, body_chunk, cta ]by default. 3-post self-replies surface normally on bsky.app's profile views. When the post body has fewer than ~10 chars of prose left after the hook, the body chunk is dropped and the result falls back to[ hook, cta ]— no padded empty post.app.bsky.embed.externallink card (title + excerpt + thumbnail). The embed attaches to "last entry," not "index 2," so the 2-entry fallback and anyatmosphere_teaser_thread_postsfilter override still ship a CTA-with-card. The hook (root) deliberately does not get an embed — title/excerpt/thumb show up via the card on the CTA reply.No new filters, no opt-in flags. The existing
atmosphere_teaser_thread_postsarray filter stays as the universal escape hatch.Body-chunk extraction
Other information:
'post_excerpt' => ''added to keep them at the 2-record shape they exercise — the publisher protocol is independent of thread length and forcing the 2-record path keeps these tests focused.atmosphere_teaser_thread_postsfilter contract is unchanged: 2..5 string entries, default value passed in is now the new 2-or-3 entry array depending on body length.Publisher's reply-stamping is untouched:replyandembedare independent fields inapp.bsky.feed.post's lexicon and coexist fine on the same record.Testing instructions:
composer lint— passes clean.npm run env-test— 167 tests, 585 assertions, all green.Changelog entry
.github/changelog/teaser-thread-3post-card.Changelog Entry Details
Significance
Type
Message
Long-form teaser threads now use a 3-post default (hook, body chunk, "continue reading" reply with a link card), so the thread reliably surfaces on bsky.app profiles and the terminal post offers a clear path back to the WordPress article.