Skip to content

Default teaser-thread to 3 posts with link-card embed on CTA#49

Open
kraftbj wants to merge 4 commits intotrunkfrom
teaser-thread-3post-card
Open

Default teaser-thread to 3 posts with link-card embed on CTA#49
kraftbj wants to merge 4 commits intotrunkfrom
teaser-thread-3post-card

Conversation

@kraftbj
Copy link
Copy Markdown
Contributor

@kraftbj kraftbj commented Apr 30, 2026

Fixes Automattic/fosse#64

Proposed changes:

Iterates on the unreleased teaser-thread long-form strategy to address two real-world UX problems FOSSE found in production:

  1. 2-post self-reply threads are functionally invisible on bsky.app. AppView bundles/hides them on profile views (getAuthorFeed?filter=posts_no_replies drops the root, posts_with_replies shows the reply but not the root). Threads live on the PDS but are unreachable via profile browsing.
  2. No link affordance on the hook. The root post had no link facet and no embed, so even when surfaced (e.g. via /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.
  • The terminal CTA reply now carries an app.bsky.embed.external link card (title + excerpt + thumbnail). The embed attaches to "last entry," not "index 2," so the 2-entry fallback and any atmosphere_teaser_thread_posts filter 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_posts array filter stays as the universal escape hatch.

Body-chunk extraction

  • Excerpt-as-hook: the chunk starts from the start of the body (curated excerpts are not sliding windows).
  • Body-as-hook: the chunk continues after the hook's cut point; hook and chunk are non-overlapping windows over the same plain-text body. Ellipsis-aware char counting handles the hard-cap path safely (no UTF-8 corruption when the trailing char is multibyte).

Other information:

  • Settings page help text updated from "two-post" to "short" thread description with link-card mention.
  • Publisher protocol tests have '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.
  • Existing atmosphere_teaser_thread_posts filter 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: reply and embed are independent fields in app.bsky.feed.post's lexicon and coexist fine on the same record.
  • Have you written new tests for your changes, if applicable?

Testing instructions:

  • composer lint — passes clean.
  • npm run env-test — 167 tests, 585 assertions, all green.
  • For end-to-end verification: publish a long-form post with a populated body to a Bluesky test account and confirm on the bsky.app profile view that the 3-post thread surfaces on the Posts tab. The terminal CTA reply should display a link card with the WordPress post's title, excerpt, and (if set) featured image.

Changelog entry

  • Already added: .github/changelog/teaser-thread-3post-card.
Changelog Entry Details

Significance

  • Minor

Type

  • Changed - for changes in existing functionality

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.

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
@kraftbj kraftbj self-assigned this Apr 30, 2026
@github-actions github-actions Bot added [Feature] Transformer AT Protocol record transformers [Feature] WP Admin Admin UI and settings [Tests] Includes Tests PR includes test changes labels Apr 30, 2026
@kraftbj kraftbj requested a review from Copilot April 30, 2026 21:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-thread output from 2 posts to 3 posts (hook, body chunk, CTA), with an automatic 2-post fallback for short bodies.
  • Attach an app.bsky.embed.external link 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.

Comment thread includes/transformer/class-post.php Outdated
Comment thread includes/wp-admin/class-admin.php Outdated
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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-thread output becomes 3 records (hook, body chunk, CTA), with a 2-record fallback for short bodies.
  • Adds an app.bsky.embed.external link-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.

Comment thread includes/functions.php Outdated
Comment thread includes/transformer/class-post.php Outdated
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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-thread output 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.external link-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.

Comment thread tests/phpunit/tests/class-test-publisher.php Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-thread output 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.external link-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 /u regex 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.

@kraftbj kraftbj requested a review from pfefferle April 30, 2026 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Transformer AT Protocol record transformers [Feature] WP Admin Admin UI and settings [Tests] Includes Tests PR includes test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

teaser-thread: default to 3 posts and add link-card embed to CTA reply

2 participants