Skip to content

fix(core): hydrate byline avatar LQIP placeholder fields (#1457)#1553

Merged
ascorbic merged 1 commit into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/1457-byline-avatar-lqip
Jun 23, 2026
Merged

fix(core): hydrate byline avatar LQIP placeholder fields (#1457)#1553
ascorbic merged 1 commit into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/1457-byline-avatar-lqip

Conversation

@marcusbellamyshaw-cell

@marcusbellamyshaw-cell marcusbellamyshaw-cell commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Content byline credits fold the avatar's storage key and alt into the result via the media LEFT JOIN, but dropped the LQIP placeholder columns (blurhash / dominant_color) that the media table stores (migration 024). So an author avatar couldn't render a low-quality image placeholder while loading, even though every other media consumer can.

This carries m.blurhash / m.dominant_color through the same media join in getContentBylines, getContentBylinesMany, and findByUserIds, surfaces them as BylineSummary.avatarBlurhash / avatarDominantColor (and in the byline API response schema), and defaults them to null on finders that don't join media — mirroring the existing avatarStorageKey / avatarAlt semantics. Purely additive: no new join, no extra round-trip, existing BylineSummary literals stay source-compatible.

Closes #1457

Type of change

  • Bug fix

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (targeted: byline.test.ts + full integration suite)
  • pnpm format has been run
  • I have added/updated tests for my changes
  • User-visible strings in the admin UI are wrapped for translation (n/a — no admin UI strings)
  • I have added a changeset

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 ultracode

Screenshots / test output

byline.test.ts: 64 passed. Targeted unit + integration suites green (only pre-existing Windows-only baseline failures unrelated to this change).

…1457)

Content byline credits fold in the avatar's storage key and alt via the
media join, but dropped the blurhash/dominant_color LQIP columns, so
author avatars couldn't render a low-quality placeholder despite the
media row storing one.

Carry m.blurhash/m.dominant_color through the same media join in
getContentBylines, getContentBylinesMany, and findByUserIds, surface
them as BylineSummary.avatarBlurhash/avatarDominantColor (and in the
byline API response schema), and default to null on finders that don't
join media. Purely additive, mirroring the existing avatarStorageKey
pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ae0fd77

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approach: This is the right change, solving the right problem, in a way that fits EmDash. The byline avatar media LEFT JOIN already folded storage_key/alt into BylineSummary but dropped the LQIP columns (blurhash/dominant_color) that migration 024 added to media. The fix carries those two columns through the same existing joins in all three byline hydration paths (getContentBylines, getContentBylinesMany, findByUserIds), surfaces them as optional BylineSummary.avatarBlurhash/avatarDominantColor, exposes them in the byline API response schema, and defaults them to null on the plain finders — mirroring the established avatarStorageKey/avatarAlt semantics. It's purely additive: no new join, no extra round-trip, no new query, optional fields + nullish() schema + patch bump = backwards-compatible. The changeset is well-written and describes the observable effect for an upgrader. Good taste.

What I checked:

  • Migration 024_media_placeholders.ts adds blurhash/dominant_color to media, and database/types.ts declares them — so the m.blurhash/m.dominant_color selects typecheck and run on both SQLite and PG.
  • All three avatar-media joins in byline.ts (lines ~933, ~1072, ~1181) are updated identically; rg confirms no other avatar-media joins exist elsewhere, so there's nothing left inconsistent.
  • ContentItem.byline / entry.data.byline (content handler item.byline = explicit[0]?.byline and query.ts data.byline = credits[0]?.byline) both derive from these same repository paths, so the LQIP fields propagate to every byline consumer — there is no separate primary-byline media join that this PR would have missed.
  • rowToByline uses row.avatar_blurhash ?? null, and the plain finders use selectAll() on _emdash_bylines (no media join), so the new fields come back as null there — the "null on plain finders" contract holds.
  • The new selects are static string literals (no interpolation) — SQL-safe; no content-table (ec_*) queries are added, so no locale-filter concern; no new indexes needed.
  • Tests cover the single-entry, batch, and findByUserIds hydration paths asserting the LQIP values are present, plus null-when-no-avatar on the join paths. The skipHydration paths share the same select list and both route through rowToByline, so the default-path coverage implies the skip path for these fields.

Conclusion: Implementation is correct, complete, and consistent. The only thing I'd touch is a minor test-coverage nicety: the plain-finder null-contract test was extended for avatarStorageKey but not for the two new fields it now also applies to. Not blocking.


Findings

  • [suggestion] packages/core/tests/unit/database/repositories/byline.test.ts:279

    This test locks in the contract that plain finders (findById, findBySlug, …) don't join media and return null for avatar-derived fields. This PR extends that exact contract to avatarBlurhash/avatarDominantColor — and rowToByline's ?? null default guarantees it, since findById uses selectAll() on _emdash_bylines with no media join (so avatar_blurhash/avatar_dominant_color are undefined on the row). But the assertion here was only extended to cover avatarStorageKey, not the two new fields. The join-path tests above cover presence; this is the one place that covers absence for the plain finders, so it's worth extending for parity.

    		expect(byId?.avatarStorageKey).toBeNull();
    		expect(byId?.avatarBlurhash).toBeNull();
    		expect(byId?.avatarDominantColor).toBeNull();
    

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-review No maintainer or bot review yet labels Jun 20, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1553

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1553

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1553

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1553

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1553

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1553

emdash

npm i https://pkg.pr.new/emdash@1553

create-emdash

npm i https://pkg.pr.new/create-emdash@1553

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1553

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1553

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1553

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1553

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1553

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1553

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1553

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1553

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1553

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1553

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1553

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1553

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1553

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1553

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1553

commit: ae0fd77

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

@ascorbic ascorbic merged commit 7fca12f into emdash-cms:main Jun 23, 2026
47 checks passed
@emdashbot emdashbot Bot mentioned this pull request Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Byline avatar hydration drops media LQIP columns (blurhash / dominant_color)

2 participants