Skip to content

Self-hosted: give Magazine a hero-plus-rows archive - #1472

Merged
feruzm merged 4 commits into
developfrom
feature/self-hosted-magazine-hero
Aug 13, 2026
Merged

Self-hosted: give Magazine a hero-plus-rows archive#1472
feruzm merged 4 commits into
developfrom
feature/self-hosted-magazine-hero

Conversation

@feruzm

@feruzm feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member

Closes #1468. Independent of #1470; either can merge first.

Magazine was tokens on the shared list, so the name promised a structure it did not have. The newest entry is now a hero (full-width 16:9 image, display headline, roomier excerpt), with the rest as ordinary rows beneath a rule.

This upgrades an existing template into a real layout rather than adding a name to the picker, so the roster count does not move. It takes the roster to four layouts across eight templates.

The frame extraction

The default archive owned fetching, paging and four failure states (nothing yet, nothing at all, failed with nothing to show, failed with pages already read). A hero archive needs all of it and none of it is about layout, so it moves to ArchiveFrame, which both archives now render inside. Each supplies only how entries are arranged.

The alternative was copying that logic into the Magazine archive, and it is exactly the copy that drifts: the retry branch there is the one that decides whether a failed page loses the reader's place in the feed.

PostCard deliberately stays the shared default, the same split Reader uses, because search results render through that seam and a hero has no meaning in a list of search hits.

Behaviour worth stating

  • A lead post with no image keeps the hero's type scale and drops the picture, so a text-led blog reads as a front page rather than a broken one.
  • A blog with exactly one post is all hero and no rows, which is correct: one post is the front page.
  • Row entrance staggering accounts for the hero taking the first slot, so rows carry the rest of their batch's positions rather than restarting.

Verified by rendering it

Built the app and served the real dist through the published image's nginx against a real account:

viewport hero hero title row title rows horizontal scroll console
1440px image + text 36px 24px 19 none clean
390px image + text 30px 20px 19 none clean

The sidebar stays, since Magazine is not one of the templates that drops it.

934 SPA tests and typecheck pass. registry.test.ts moves Magazine out of the CSS-only set and pins what it overrides; the failure-states source guard now points at ArchiveFrame, which is where the outcome handling lives.

Magazine was a colour and type treatment on the shared list, so its name
promised a structure it did not have. The newest entry now renders as a
hero (full-width image, display headline, roomier excerpt) with the rest as
ordinary rows.

The feed frame is extracted so this costs no duplication: fetching, paging
and the four failure states move to ArchiveFrame, and both the default
archive and Magazine's supply only the layout. Copying that logic was the
alternative, and it is exactly the copy that drifts and then loses a
reader's place in the feed on a failed page.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Hero not full-width ✓ Resolved 🐞 Bug ≡ Correctness
Description
MagazineHero is rendered as a direct child of .blog-posts-list, which becomes a multi-column grid
when data-list-type="grid" (the default). Because the hero `` doesn’t span all grid columns, it
will render in only one column on tablet/desktop, breaking the intended hero-plus-rows layout.
Code

apps/self-hosted/src/themes/magazine/magazine-archive.tsx[R82-85]

+    <article className="magazine-hero pb-8 mb-2 border-b border-theme">
+      {imageUrl && (
+        <Link
+          to="/$author/$permlink"
Relevance

●●● Strong

Layout break in grid mode; team has accepted similar responsive/layout fixes in theme components.

PR-#1446
PR-#1447
PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ArchiveFrame wraps MagazineArchive’s children in .blog-posts-list. CSS switches
.blog-posts-list to a multi-column grid when data-list-type="grid", and data-list-type
defaults to grid. Magazine sets the grid column counts >1 at breakpoints, but the hero `` has no
grid-column spanning rule, so it will only occupy one column.

apps/self-hosted/src/features/blog/components/archive-frame.tsx[90-97]
apps/self-hosted/src/styles/components.css[246-273]
apps/self-hosted/src/core/apply-config-dom.ts[227-230]
apps/self-hosted/src/styles/themes/magazine.css[65-76]
apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Magazine theme’s archive now renders a hero entry first, but it is placed inside `.blog-posts-list` (the shared archive wrapper). When the instance is configured (or defaults) to `data-list-type="grid"`, `.blog-posts-list` becomes a multi-column CSS grid. The hero `<article>` lacks a `grid-column: 1 / -1` span, so it becomes a single grid cell instead of a full-width hero.
## Issue Context
- `data-list-type` defaults to `grid` when config omits `listType`, so this can happen on default installs.
- Magazine theme sets multiple grid columns at breakpoints (`--theme-grid-columns-tablet: 2`, `--theme-grid-columns-desktop: 3`).
- The PR’s stated intent is “full-width 16:9 image” hero + rows.
## Fix options (pick one)
1) **Make hero span the full grid width** in grid mode (minimal behavioral change):
- Add `className`/style to the hero wrapper, e.g. Tailwind `col-span-full` on the `<article>`.
2) **Force Magazine archive to always be list layout** (matches “hero + rows” semantics):
- Add `unsupportedOptions: ['listType']` to the magazine manifest, and add a Magazine-specific CSS override so `[data-style-template="magazine"][data-list-type="grid"] .blog-posts-list` renders as a column flex (protects existing stored configs).
## Fix Focus Areas
- apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-101]
- apps/self-hosted/src/styles/components.css[246-273]
- apps/self-hosted/src/core/apply-config-dom.ts[227-230]
- apps/self-hosted/src/styles/themes/magazine.css[65-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Hero not full-width ✓ Resolved 🐞 Bug ≡ Correctness
Description
MagazineHero is rendered as a direct child of .blog-posts-list, which becomes a multi-column grid
when data-list-type="grid" (the default). Because the hero `` doesn’t span all grid columns, it
will render in only one column on tablet/desktop, breaking the intended hero-plus-rows layout.
Code

apps/self-hosted/src/themes/magazine/magazine-archive.tsx[R82-85]

+    <article className="magazine-hero pb-8 mb-2 border-b border-theme">
+      {imageUrl && (
+        <Link
+          to="/$author/$permlink"
Relevance

●●● Strong

Layout break in grid mode; team has accepted similar responsive/layout fixes in theme components.

PR-#1446
PR-#1447
PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ArchiveFrame wraps MagazineArchive’s children in .blog-posts-list. CSS switches
.blog-posts-list to a multi-column grid when data-list-type="grid", and data-list-type
defaults to grid. Magazine sets the grid column counts >1 at breakpoints, but the hero `` has no
grid-column spanning rule, so it will only occupy one column.

apps/self-hosted/src/features/blog/components/archive-frame.tsx[90-97]
apps/self-hosted/src/styles/components.css[246-273]
apps/self-hosted/src/core/apply-config-dom.ts[227-230]
apps/self-hosted/src/styles/themes/magazine.css[65-76]
apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Magazine theme’s archive now renders a hero entry first, but it is placed inside `.blog-posts-list` (the shared archive wrapper). When the instance is configured (or defaults) to `data-list-type="grid"`, `.blog-posts-list` becomes a multi-column CSS grid. The hero `<article>` lacks a `grid-column: 1 / -1` span, so it becomes a single grid cell instead of a full-width hero.
## Issue Context
- `data-list-type` defaults to `grid` when config omits `listType`, so this can happen on default installs.
- Magazine theme sets multiple grid columns at breakpoints (`--theme-grid-columns-tablet: 2`, `--theme-grid-columns-desktop: 3`).
- The PR’s stated intent is “full-width 16:9 image” hero + rows.
## Fix options (pick one)
1) **Make hero span the full grid width** in grid mode (minimal behavioral change):
- Add `className`/style to the hero wrapper, e.g. Tailwind `col-span-full` on the `<article>`.
2) **Force Magazine archive to always be list layout** (matches “hero + rows” semantics):
- Add `unsupportedOptions: ['listType']` to the magazine manifest, and add a Magazine-specific CSS override so `[data-style-template="magazine"][data-list-type="grid"] .blog-posts-list` renders as a column flex (protects existing stored configs).
## Fix Focus Areas
- apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-101]
- apps/self-hosted/src/styles/components.css[246-273]
- apps/self-hosted/src/core/apply-config-dom.ts[227-230]
- apps/self-hosted/src/styles/themes/magazine.css[65-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Hero not full-width ✓ Resolved 🐞 Bug ≡ Correctness
Description
MagazineHero is rendered as a direct child of .blog-posts-list, which becomes a multi-column grid
when data-list-type="grid" (the default). Because the hero `` doesn’t span all grid columns, it
will render in only one column on tablet/desktop, breaking the intended hero-plus-rows layout.
Code

apps/self-hosted/src/themes/magazine/magazine-archive.tsx[R82-85]

+    <article className="magazine-hero pb-8 mb-2 border-b border-theme">
+      {imageUrl && (
+        <Link
+          to="/$author/$permlink"
Relevance

●●● Strong

Layout break in grid mode; team has accepted similar responsive/layout fixes in theme components.

PR-#1446
PR-#1447
PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ArchiveFrame wraps MagazineArchive’s children in .blog-posts-list. CSS switches
.blog-posts-list to a multi-column grid when data-list-type="grid", and data-list-type
defaults to grid. Magazine sets the grid column counts >1 at breakpoints, but the hero `` has no
grid-column spanning rule, so it will only occupy one column.

apps/self-hosted/src/features/blog/components/archive-frame.tsx[90-97]
apps/self-hosted/src/styles/components.css[246-273]
apps/self-hosted/src/core/apply-config-dom.ts[227-230]
apps/self-hosted/src/styles/themes/magazine.css[65-76]
apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Magazine theme’s archive now renders a hero entry first, but it is placed inside `.blog-posts-list` (the shared archive wrapper). When the instance is configured (or defaults) to `data-list-type="grid"`, `.blog-posts-list` becomes a multi-column CSS grid. The hero `<article>` lacks a `grid-column: 1 / -1` span, so it becomes a single grid cell instead of a full-width hero.
## Issue Context
- `data-list-type` defaults to `grid` when config omits `listType`, so this can happen on default installs.
- Magazine theme sets multiple grid columns at breakpoints (`--theme-grid-columns-tablet: 2`, `--theme-grid-columns-desktop: 3`).
- The PR’s stated intent is “full-width 16:9 image” hero + rows.
## Fix options (pick one)
1) **Make hero span the full grid width** in grid mode (minimal behavioral change):
 - Add `className`/style to the hero wrapper, e.g. Tailwind `col-span-full` on the `<article>`.
2) **Force Magazine archive to always be list layout** (matches “hero + rows” semantics):
 - Add `unsupportedOptions: ['listType']` to the magazine manifest, and add a Magazine-specific CSS override so `[data-style-template="magazine"][data-list-type="grid"] .blog-posts-list` renders as a column flex (protects existing stored configs).
## Fix Focus Areas
- apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-101]
- apps/self-hosted/src/styles/components.css[246-273]
- apps/self-hosted/src/core/apply-config-dom.ts[227-230]
- apps/self-hosted/src/styles/themes/magazine.css[65-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@feruzm, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 99dbc6e8-852b-4846-ac67-8488ca96b730

📥 Commits

Reviewing files that changed from the base of the PR and between a2a4403 and bc4e3ac.

📒 Files selected for processing (8)
  • apps/self-hosted/src/features/blog/components/archive-frame.tsx
  • apps/self-hosted/src/features/blog/components/blog-post-item.tsx
  • apps/self-hosted/src/features/blog/components/blog-posts-list.tsx
  • apps/self-hosted/src/features/shared/failure-states.test.ts
  • apps/self-hosted/src/styles/components.css
  • apps/self-hosted/src/themes/magazine/magazine-archive.tsx
  • apps/self-hosted/src/themes/registry.test.ts
  • apps/self-hosted/src/themes/registry.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 13, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Magazine theme: hero+rows archive via shared ArchiveFrame

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Extract archive fetching, paging, and failure/empty handling into a shared ArchiveFrame.
• Implement Magazine archive layout with a lead hero post plus standard rows.
• Wire Magazine’s archive override into the theme registry and update guards/tests.
Diagram

graph TD
  route["/blog route"] --> theme["useThemeComponents"] --> arch["ArchiveList (theme)"] --> frame["ArchiveFrame"] --> feed("useArchiveFeed")
  arch --> layout["Archive layout"] --> card["PostCard (shared)"]
  layout --> hero["MagazineHero"]
  frame --> ui["Empty/Error UI"]
  frame --> bottom["DetectBottom"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Duplicate feed/outcome logic inside MagazineArchive
  • ➕ Fastest to implement initially
  • ➕ No new shared component surface
  • ➖ High drift risk (retry/outcome logic is subtle)
  • ➖ Increases likelihood of inconsistent failure-state behavior between archives
  • ➖ Harder to maintain test coverage and invariants across multiple implementations
2. Extract a generic hook-only layer (no frame component)
  • ➕ Keeps rendering fully in the archive components
  • ➕ Potentially simpler composition for some layouts
  • ➖ Still requires every archive to re-implement the same outcome/paging UI and sentinel behavior
  • ➖ Higher chance of regressions like retry storms or losing already-rendered pages

Recommendation: Keep the current ArchiveFrame extraction. It centralizes the non-layout concerns (query outcome semantics, retry selection, sentinel unmounting to avoid retry loops) while letting themes vary layout safely. The render-prop API is a good fit because it shares paging/index-stagger semantics without forcing a specific markup structure.

Files changed (6) +306 / -107

Enhancement (2) +144 / -1
magazine-archive.tsxAdd MagazineArchive with lead hero post and rows beneath +133/-0

Add MagazineArchive with lead hero post and rows beneath

• Implements a Magazine-specific archive layout: first entry renders as a hero with optional image, large headline, metadata and excerpt; remaining entries render as shared 'PostCard' rows. Ensures row entrance staggering accounts for the hero consuming index 0.

apps/self-hosted/src/themes/magazine/magazine-archive.tsx

registry.tsRegister MagazineArchive as the Magazine theme’s ArchiveList override +11/-1

Register MagazineArchive as the Magazine theme’s ArchiveList override

• Wires the Magazine manifest to override only the 'ArchiveList' seam with 'MagazineArchive', leaving other seams (notably 'PostCard') as defaults to preserve search-result rendering semantics.

apps/self-hosted/src/themes/registry.ts

Refactor (2) +146 / -101
archive-frame.tsxIntroduce shared ArchiveFrame for feed/outcome/paging behavior +132/-0

Introduce shared ArchiveFrame for feed/outcome/paging behavior

• Adds a reusable frame component that owns archive querying, infinite scroll paging, and the full set of empty/error states. Preserves already-rendered pages during later-page failures and exposes 'batchIndexOf' to keep entrance staggering stable across pagination and filter changes.

apps/self-hosted/src/features/blog/components/archive-frame.tsx

blog-posts-list.tsxRefactor default archive to render inside ArchiveFrame +14/-101

Refactor default archive to render inside ArchiveFrame

• Replaces inline feed/outcome logic with 'ArchiveFrame' and keeps this component focused on the default layout (mapping posts to themed 'PostCard'). Uses 'batchIndexOf' for consistent per-page animation staggering.

apps/self-hosted/src/features/blog/components/blog-posts-list.tsx

Tests (2) +16 / -5
failure-states.test.tsPoint failure-state guards at ArchiveFrame as the new feed owner +3/-3

Point failure-state guards at ArchiveFrame as the new feed owner

• Updates guarded claim paths and reading surface lists to reference 'archive-frame.tsx'. Retains assertions preventing retry storms (sentinel unmount while stale) and ensuring retry targets the correct failed operation.

apps/self-hosted/src/features/shared/failure-states.test.ts

registry.test.tsUpdate theme registry expectations for Magazine overriding only ArchiveList +13/-2

Update theme registry expectations for Magazine overriding only ArchiveList

• Moves Magazine out of the CSS-only set and asserts it overrides 'ArchiveList' while keeping 'PostCard', 'Shell', and 'Sidebar' on shared defaults. Extends the set of layout themes to include Magazine.

apps/self-hosted/src/themes/registry.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd6174cf4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const postSearch = { raw: undefined };

return (
<article className="magazine-hero pb-8 mb-2 border-b border-theme">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make the magazine hero span the configured grid

When Magazine uses the supported listType: 'grid'—or the value is absent and apply-config-dom.ts defaults it to gridArchiveFrame makes .blog-posts-list a two- or three-column grid, but this article has no full-column span. The hero therefore occupies only the first grid cell and later posts appear beside it, despite the image sizes and the new layout both assuming a full-width hero. Make the hero span all grid columns so the archive retains its hero-first structure under this configuration.

Useful? React with 👍 / 👎.

@qodo-code-review

qodo-code-review Bot commented Aug 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Hero not full-width ✓ Resolved 🐞 Bug ≡ Correctness
Description
MagazineHero is rendered as a direct child of .blog-posts-list, which becomes a multi-column grid
when data-list-type="grid" (the default). Because the hero <article> doesn’t span all grid
columns, it will render in only one column on tablet/desktop, breaking the intended hero-plus-rows
layout.
Code

apps/self-hosted/src/themes/magazine/magazine-archive.tsx[R82-85]

+    <article className="magazine-hero pb-8 mb-2 border-b border-theme">
+      {imageUrl && (
+        <Link
+          to="/$author/$permlink"
Relevance

●●● Strong

Layout break in grid mode; team has accepted similar responsive/layout fixes in theme components.

PR-#1446
PR-#1447
PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ArchiveFrame wraps MagazineArchive’s children in .blog-posts-list. CSS switches
.blog-posts-list to a multi-column grid when data-list-type="grid", and data-list-type
defaults to grid. Magazine sets the grid column counts >1 at breakpoints, but the hero <article>
has no grid-column spanning rule, so it will only occupy one column.

apps/self-hosted/src/features/blog/components/archive-frame.tsx[90-97]
apps/self-hosted/src/styles/components.css[246-273]
apps/self-hosted/src/core/apply-config-dom.ts[227-230]
apps/self-hosted/src/styles/themes/magazine.css[65-76]
apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Magazine theme’s archive now renders a hero entry first, but it is placed inside `.blog-posts-list` (the shared archive wrapper). When the instance is configured (or defaults) to `data-list-type="grid"`, `.blog-posts-list` becomes a multi-column CSS grid. The hero `<article>` lacks a `grid-column: 1 / -1` span, so it becomes a single grid cell instead of a full-width hero.

## Issue Context
- `data-list-type` defaults to `grid` when config omits `listType`, so this can happen on default installs.
- Magazine theme sets multiple grid columns at breakpoints (`--theme-grid-columns-tablet: 2`, `--theme-grid-columns-desktop: 3`).
- The PR’s stated intent is “full-width 16:9 image” hero + rows.

## Fix options (pick one)
1) **Make hero span the full grid width** in grid mode (minimal behavioral change):
  - Add `className`/style to the hero wrapper, e.g. Tailwind `col-span-full` on the `<article>`.
2) **Force Magazine archive to always be list layout** (matches “hero + rows” semantics):
  - Add `unsupportedOptions: ['listType']` to the magazine manifest, and add a Magazine-specific CSS override so `[data-style-template="magazine"][data-list-type="grid"] .blog-posts-list` renders as a column flex (protects existing stored configs).

## Fix Focus Areas
- apps/self-hosted/src/themes/magazine/magazine-archive.tsx[81-101]
- apps/self-hosted/src/styles/components.css[246-273]
- apps/self-hosted/src/core/apply-config-dom.ts[227-230]
- apps/self-hosted/src/styles/themes/magazine.css[65-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 75 rules
✅ Skills: 6 invoked
  add-feature
  add-query
  add-sdk-mutation
  add-test
  code-review
  debug
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 11/18, lines 413/200; both must reach the floor). Router rationale: This adds a shared archive state/paging abstraction plus a new theme-specific hero layout and registry integration, creating multiple independent logic and rendering paths where subtle defects could be missed in one pass.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/self-hosted/src/themes/magazine/magazine-archive.tsx Outdated
feruzm added 2 commits August 13, 2026 10:12
…cks shrink

In the grid feed the hero was an ordinary cell: one column wide with the
second post beside it, which is the opposite of a hero. It now spans every
column.

That exposed an older bug underneath. A bare 1fr is minmax(auto, 1fr), so a
track cannot shrink below its content's min-content width; the post cards
are wider than a third of the reading measure, so three tracks blew out to
1179px inside a 768px column. Every template's grid feed had it. The tracks
are now minmax(0, 1fr).
@feruzm

feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Fixed at 99bb3a9, and chasing it turned up an older bug underneath.

The hero was an ordinary grid cell. Reproduced before fixing: with listType: grid the archive is a real CSS grid, so the hero rendered 386px wide in a 385px column with the second post beside it at the same offset. It now carries col-span-full, which is inert in list mode where the archive is a flex column.

What that exposed. With the hero spanning the row, it came out 1221px wide inside a 768px container. The cause is not the hero: a bare 1fr is minmax(auto, 1fr), so a track cannot shrink below its content's min-content width, and the post cards are wider than a third of the reading measure. Checked against a theme with no hero at all to be sure it was not mine:

template + grid container tracks overflow
medium (before) 768 3 x 377 = 1179 yes, 1179 into 768
minimal (before) 768 2 x 374 = 748 no, two columns happen to fit
magazine (before) 768 3 x 385 yes, and the hero made it visible

So every template's grid feed had this, and two columns hid it by fitting. Tracks are now minmax(0, 1fr):

template + grid tracks hero overflows right
medium 3 x 240 n/a no
minimal 2 x 374 n/a no
magazine 3 x 235 768, full row no

The hero image also stops under-fetching as a result: it was displaying at 1221px while the browser picked an 800px candidate, and now displays at 768px with the same 800px candidate.

One thing I did not fix. A residual overflow remains inside the cards themselves (scrollWidth 905 against a 768 container on medium), where content inside a 240px card pushes wider. It predates this PR, shows identically on templates I did not touch, and is a different fix. Worth knowing that no tenant uses the grid feed at all: all twelve are on list, which is part of why #1471 proposes retiring the option.

940 SPA tests and typecheck pass.

@feruzm

feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Fixed at bc4e3ac, and there is a related discovery below that raises the stakes on the hero fix.

The metadata row. Reproduced before fixing: in a 235px card the row wanted 386px and spilled 151px over its neighbour, so the minmax(0, 1fr) change traded a whole-grid overflow for a per-card one. The row now wraps (flex-wrap with a gap-y), and the author name truncates inside a min-w-0 link so a long name cannot push it out either.

Verified across both feed modes, since the row is shared by every template:

template / feed card row fits row height
magazine / grid 235 yes 44px (wrapped to two lines)
magazine / list 768 yes 24px (one line, unchanged)
medium / grid 240 yes 44px
medium / list 768 yes 24px

No card spill and no page-level horizontal scroll in any of them. Wrapping only engages where there is not enough room, so the list feed every tenant actually uses is byte-identical.

The related discovery. Checking Codex's note on the same file, the two defaults for this setting disagree:

  • TenantService.getDefaultConfig writes layout.listType: 'list'
  • apply-config-dom.ts resolves data-list-type with a fallback of 'grid'

So a config that simply lacks the key renders as a grid, not a list. Every one of the twelve live tenants carries the key explicitly, so nobody is affected today, but a hand-edited config.json on a self-hosted instance would land in the grid feed by accident, which is exactly where the hero bug and the track blowout lived. That is not this PR's to change, since flipping the fallback alters rendering for any such config; I have added it to #1471, where retiring the option removes the disagreement entirely.

The two bot findings on this file are the same hero-span issue and were already fixed at 99bb3a9, one commit before their review ran.

940 SPA tests and typecheck pass.

@feruzm
feruzm merged commit d9a12ae into develop Aug 13, 2026
12 checks passed
@feruzm
feruzm deleted the feature/self-hosted-magazine-hero branch August 13, 2026 10:36
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.

Self-hosted: give Magazine a hero-plus-rows archive

1 participant