Skip to content

Self-hosted: add a Gallery layout for image-led blogs - #1470

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

Self-hosted: add a Gallery layout for image-led blogs#1470
feruzm merged 4 commits into
developfrom
feature/self-hosted-gallery-theme

Conversation

@feruzm

@feruzm feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member

Closes #1467.

The roster had seven templates but three layouts. Five of them (medium, minimal, magazine, developer, modern-gradient) carry no component overrides at all: they render the identical tree and differ in tokens. Nothing served a blog whose posts are pictures, which got a text list with a thumbnail beside it.

The layout

The archive becomes a grid of covers, with the title and date as a quiet caption underneath and no sidebar. The palette is a gallery wall on purpose: near-neutral grounds and a desaturated slate accent, because a saturated accent beside a photograph fights it. In dark mode the wall drops to #121212 so the pictures carry the brightness.

Gallery is the first layout-level theme that keeps the shared shell. Its structure is three CSS rules the theme owns, so only two things are components: the tile, and a Sidebar that renders nothing. That last one is a seam override rather than a CSS hide on purpose, because the default sidebar fetches followers, following and chain data, and hiding it in CSS would still run all of that for a column nobody sees.

A post with no usable image is not dropped and not given an empty box. Some posts in an image-led blog are text, and a hole in a grid reads as a bug, so those tiles fall back to a typeset panel carrying the title and first lines.

One shared-component change

BlogPage gains a blog-page-measure class beside its existing max-w-3xl. Nothing changes for any other template; it is a styling hook so Gallery can widen that one wrapper from its own stylesheet. Making BlogPage read --theme-content-width instead was the tempting fix and would have been wrong: every other template declares 640px to 760px there, so it would have narrowed every existing blog's archive as a side effect of adding a theme.

Verified by rendering it

Built the SPA and served the real dist through the published image's nginx with a config for an image-heavy account:

  • four columns at 1440px, one on a 390px phone, no horizontal scroll at either;
  • the sidebar column collapsed rather than left empty;
  • 21 tiles, images loading, no console errors;
  • dark config confirmed: ground #121212 and the caption tokens from the dark block.

The three layout rules are pinned by a new guard test, because they are silent when broken: delete the grid rule and Gallery becomes a single column that looks like a design choice, and the guard also asserts every layout selector keeps its :root prefix, since components.css is imported after the themes and would otherwise win at equal specificity.

939 SPA tests, 465 hosting API tests, typecheck and a production build all pass. Roster, editor label, token and card-treatment guards updated for the eighth template.

Screenshots (desktop light, desktop dark, phone) attached in the next comment.

Summary by CodeRabbit

  • New Features
    • Added a Gallery style template for picture-led blogs.
    • Introduced a responsive image-grid layout with light and dark theme support.
    • Added gallery post cards with cover images, titles, summaries, dates, and links.
    • Added the Gallery option to the style-template configuration menu.
    • Gallery layouts use a single-column page and hide the sidebar for a focused presentation.

The roster had seven templates but three layouts: five of them render the
identical tree and differ only in tokens. Nothing served a blog whose posts
are pictures, which got a text list with a thumbnail beside it.

Gallery makes the archive a grid of covers with the title and date as a
quiet caption underneath, and no sidebar. It is the first layout-level
theme that keeps the shared shell: the structure is three CSS rules the
theme owns, so only the tile and the absent sidebar are components. A post
with no usable image falls back to a typeset panel rather than leaving a
hole in the grid.
@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 (8)

Grey Divider


Remediation recommended

1. Mismatched img sizes hint ✓ Resolved 🐞 Bug ➹ Performance
Description
GalleryPostCard hard-codes the ` hint even though Gallery uses a content-width-capped auto-fill`
grid, so the hint often overestimates tile width and makes browsers download larger srcset
candidates than necessary. This reintroduces the sizing-drift risk the existing grid sizing helper
was designed to avoid.
Code

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[R67-70]

+              srcSet={buildSrcSet(imageUrl) || undefined}
+              /* One tile wide: the grid packs at a 260px minimum and grows,
+                 so a third of a wide window is the realistic upper bound. */
+              sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
Relevance

●●● Strong

Hard-coded img sizes drift was previously flagged and accepted; team prefers deriving sizes from
actual grid/layout.

PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Gallery layout uses an auto-fill grid (repeat(auto-fill, minmax(...))) and caps the page
width via --theme-content-width, so a fixed 33vw assumption can substantially overestimate the
actual tile width on wide viewports. The codebase already has a helper (computeThemeGridSizes)
used by the default post card to avoid hard-coded sizes strings.

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
apps/self-hosted/src/styles/themes/gallery.css[82-95]
apps/self-hosted/src/styles/themes/gallery.css[146-155]
apps/self-hosted/src/themes/grid-sizes.ts[1-31]
apps/self-hosted/src/features/blog/components/blog-post-item.tsx[93-112]
PR-#1463

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

## Issue description
`GalleryPostCard` uses a hard-coded `sizes` attribute (`50vw`/`33vw`), but the Gallery archive layout is an `auto-fill` grid inside a wrapper capped at `--theme-content-width`. On wide screens this causes the browser to assume a much larger rendered image width than reality and fetch oversized `srcset` candidates.
## Issue Context
The codebase already introduced a theme-aware sizing mechanism (`useThemeGridSizes` / `computeThemeGridSizes`) to avoid hard-coded column assumptions drifting from theme/layout reality.
## Fix approach
Choose one of these (in descending preference):
1. **Make `sizes` reflect Gallery’s real layout contract** by expressing it in CSS terms using `min()`/`calc()` and the theme variables you already define (e.g., `--theme-content-width`, `--theme-layout-container-padding`, `--theme-grid-gap`) so it accounts for the content-width cap.
2. If you want to reuse existing infrastructure, **switch GalleryPostCard to use `useThemeGridSizes()`** (and consider whether `computeThemeGridSizes` should be extended to better model content-width caps / auto-fill layouts).
Also consider memoizing `srcSet` like `BlogPostItem` does, to avoid recomputing it on re-renders.
## Fix Focus Areas
- apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
- apps/self-hosted/src/styles/themes/gallery.css[82-95]
- apps/self-hosted/src/styles/themes/gallery.css[146-155]
- apps/self-hosted/src/themes/grid-sizes.ts[15-31]

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


2. Mismatched img sizes hint ✓ Resolved 🐞 Bug ➹ Performance
Description
GalleryPostCard hard-codes the ` hint even though Gallery uses a content-width-capped auto-fill`
grid, so the hint often overestimates tile width and makes browsers download larger srcset
candidates than necessary. This reintroduces the sizing-drift risk the existing grid sizing helper
was designed to avoid.
Code

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[R67-70]

+              srcSet={buildSrcSet(imageUrl) || undefined}
+              /* One tile wide: the grid packs at a 260px minimum and grows,
+                 so a third of a wide window is the realistic upper bound. */
+              sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
Relevance

●●● Strong

Hard-coded img sizes drift was previously flagged and accepted; team prefers deriving sizes from
actual grid/layout.

PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Gallery layout uses an auto-fill grid (repeat(auto-fill, minmax(...))) and caps the page
width via --theme-content-width, so a fixed 33vw assumption can substantially overestimate the
actual tile width on wide viewports. The codebase already has a helper (computeThemeGridSizes)
used by the default post card to avoid hard-coded sizes strings.

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
apps/self-hosted/src/styles/themes/gallery.css[82-95]
apps/self-hosted/src/styles/themes/gallery.css[146-155]
apps/self-hosted/src/themes/grid-sizes.ts[1-31]
apps/self-hosted/src/features/blog/components/blog-post-item.tsx[93-112]
PR-#1463

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

## Issue description
`GalleryPostCard` uses a hard-coded `sizes` attribute (`50vw`/`33vw`), but the Gallery archive layout is an `auto-fill` grid inside a wrapper capped at `--theme-content-width`. On wide screens this causes the browser to assume a much larger rendered image width than reality and fetch oversized `srcset` candidates.
## Issue Context
The codebase already introduced a theme-aware sizing mechanism (`useThemeGridSizes` / `computeThemeGridSizes`) to avoid hard-coded column assumptions drifting from theme/layout reality.
## Fix approach
Choose one of these (in descending preference):
1. **Make `sizes` reflect Gallery’s real layout contract** by expressing it in CSS terms using `min()`/`calc()` and the theme variables you already define (e.g., `--theme-content-width`, `--theme-layout-container-padding`, `--theme-grid-gap`) so it accounts for the content-width cap.
2. If you want to reuse existing infrastructure, **switch GalleryPostCard to use `useThemeGridSizes()`** (and consider whether `computeThemeGridSizes` should be extended to better model content-width caps / auto-fill layouts).
Also consider memoizing `srcSet` like `BlogPostItem` does, to avoid recomputing it on re-renders.
## Fix Focus Areas
- apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
- apps/self-hosted/src/styles/themes/gallery.css[82-95]
- apps/self-hosted/src/styles/themes/gallery.css[146-155]
- apps/self-hosted/src/themes/grid-sizes.ts[15-31]

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


3. Mismatched img sizes hint ✓ Resolved 🐞 Bug ➹ Performance
Description
GalleryPostCard hard-codes the ` hint even though Gallery uses a content-width-capped auto-fill`
grid, so the hint often overestimates tile width and makes browsers download larger srcset
candidates than necessary. This reintroduces the sizing-drift risk the existing grid sizing helper
was designed to avoid.
Code

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[R67-70]

+              srcSet={buildSrcSet(imageUrl) || undefined}
+              /* One tile wide: the grid packs at a 260px minimum and grows,
+                 so a third of a wide window is the realistic upper bound. */
+              sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
Relevance

●●● Strong

Hard-coded img sizes drift was previously flagged and accepted; team prefers deriving sizes from
actual grid/layout.

PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Gallery layout uses an auto-fill grid (repeat(auto-fill, minmax(...))) and caps the page
width via --theme-content-width, so a fixed 33vw assumption can substantially overestimate the
actual tile width on wide viewports. The codebase already has a helper (computeThemeGridSizes)
used by the default post card to avoid hard-coded sizes strings.

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
apps/self-hosted/src/styles/themes/gallery.css[82-95]
apps/self-hosted/src/styles/themes/gallery.css[146-155]
apps/self-hosted/src/themes/grid-sizes.ts[1-31]
apps/self-hosted/src/features/blog/components/blog-post-item.tsx[93-112]
PR-#1463

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

## Issue description
`GalleryPostCard` uses a hard-coded `sizes` attribute (`50vw`/`33vw`), but the Gallery archive layout is an `auto-fill` grid inside a wrapper capped at `--theme-content-width`. On wide screens this causes the browser to assume a much larger rendered image width than reality and fetch oversized `srcset` candidates.
## Issue Context
The codebase already introduced a theme-aware sizing mechanism (`useThemeGridSizes` / `computeThemeGridSizes`) to avoid hard-coded column assumptions drifting from theme/layout reality.
## Fix approach
Choose one of these (in descending preference):
1. **Make `sizes` reflect Gallery’s real layout contract** by expressing it in CSS terms using `min()`/`calc()` and the theme variables you already define (e.g., `--theme-content-width`, `--theme-layout-container-padding`, `--theme-grid-gap`) so it accounts for the content-width cap.
2. If you want to reuse existing infrastructure, **switch GalleryPostCard to use `useThemeGridSizes()`** (and consider whether `computeThemeGridSizes` should be extended to better model content-width caps / auto-fill layouts).
Also consider memoizing `srcSet` like `BlogPostItem` does, to avoid recomputing it on re-renders.
## Fix Focus Areas
- apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
- apps/self-hosted/src/styles/themes/gallery.css[82-95]
- apps/self-hosted/src/styles/themes/gallery.css[146-155]
- apps/self-hosted/src/themes/grid-sizes.ts[15-31]

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


View medium (1)
4. Mismatched img sizes hint ✓ Resolved 🐞 Bug ➹ Performance
Description
GalleryPostCard hard-codes the ` hint even though Gallery uses a content-width-capped auto-fill`
grid, so the hint often overestimates tile width and makes browsers download larger srcset
candidates than necessary. This reintroduces the sizing-drift risk the existing grid sizing helper
was designed to avoid.
Code

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[R67-70]

+              srcSet={buildSrcSet(imageUrl) || undefined}
+              /* One tile wide: the grid packs at a 260px minimum and grows,
+                 so a third of a wide window is the realistic upper bound. */
+              sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
Relevance

●●● Strong

Hard-coded img sizes drift was previously flagged and accepted; team prefers deriving sizes from
actual grid/layout.

PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Gallery layout uses an auto-fill grid (repeat(auto-fill, minmax(...))) and caps the page
width via --theme-content-width, so a fixed 33vw assumption can substantially overestimate the
actual tile width on wide viewports. The codebase already has a helper (computeThemeGridSizes)
used by the default post card to avoid hard-coded sizes strings.

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
apps/self-hosted/src/styles/themes/gallery.css[82-95]
apps/self-hosted/src/styles/themes/gallery.css[146-155]
apps/self-hosted/src/themes/grid-sizes.ts[1-31]
apps/self-hosted/src/features/blog/components/blog-post-item.tsx[93-112]
PR-#1463

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

## Issue description
`GalleryPostCard` uses a hard-coded `sizes` attribute (`50vw`/`33vw`), but the Gallery archive layout is an `auto-fill` grid inside a wrapper capped at `--theme-content-width`. On wide screens this causes the browser to assume a much larger rendered image width than reality and fetch oversized `srcset` candidates.
## Issue Context
The codebase already introduced a theme-aware sizing mechanism (`useThemeGridSizes` / `computeThemeGridSizes`) to avoid hard-coded column assumptions drifting from theme/layout reality.
## Fix approach
Choose one of these (in descending preference):
1. **Make `sizes` reflect Gallery’s real layout contract** by expressing it in CSS terms using `min()`/`calc()` and the theme variables you already define (e.g., `--theme-content-width`, `--theme-layout-container-padding`, `--theme-grid-gap`) so it accounts for the content-width cap.
2. If you want to reuse existing infrastructure, **switch GalleryPostCard to use `useThemeGridSizes()`** (and consider whether `computeThemeGridSizes` should be extended to better model content-width caps / auto-fill layouts).
Also consider memoizing `srcSet` like `BlogPostItem` does, to avoid recomputing it on re-renders.
## Fix Focus Areas
- apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
- apps/self-hosted/src/styles/themes/gallery.css[82-95]
- apps/self-hosted/src/styles/themes/gallery.css[146-155]
- apps/self-hosted/src/themes/grid-sizes.ts[15-31]

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



Informational

5. gallery-layout test under src/ 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under apps/self-hosted/src/ instead of the required src/specs/
test hierarchy. This violates the repository test placement convention and can break test
discovery/maintenance expectations.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-4]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, it } from 'vitest';
Relevance

● Weak

Prior reviews rejected moving co-located tests into src/specs; convention enforcement not adopted.

PR-#1437
PR-#1442
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668281 requires tests to be placed under the corresponding src/specs/ directory
rather than being co-located with source. The added test file is located at
apps/self-hosted/src/styles/gallery-layout.test.ts, which is under src/.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

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

## Issue description
A new test file was added under `apps/self-hosted/src/` (`apps/self-hosted/src/styles/gallery-layout.test.ts`). Tests must not be co-located with source files and must live under the corresponding `src/specs/` tree.
## Issue Context
This test reads `apps/self-hosted/src/styles/themes/gallery.css` from disk. Moving the test will require updating the relative `join(HERE, ...)` path used to locate that CSS file.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


6. gallery-layout uses .test.ts 📜 Skill insight ⚙ Maintainability
Description
The new test file is named with the .test.ts pattern rather than the required
.spec.ts/.spec.tsx naming convention. This can prevent standard test tooling and conventions
from being applied consistently.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-2]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
Relevance

● Weak

Repo has rejected renaming .test.ts to .spec.ts in multiple reviews; naming convention not enforced.

PR-#1437
PR-#1463
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668286 requires tests to be named using .spec.ts or .spec.tsx. The added file
name is gallery-layout.test.ts, which does not match the required pattern.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

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 new test file uses the `.test.ts` naming pattern, but test files must follow the `.spec.ts` / `.spec.tsx` naming convention.
## Issue Context
After renaming, ensure any tooling references or imports (if any) still resolve, and update the filesystem-based CSS path logic if the file is also moved.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


7. gallery-layout test under src/ 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under apps/self-hosted/src/ instead of the required src/specs/
test hierarchy. This violates the repository test placement convention and can break test
discovery/maintenance expectations.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-4]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, it } from 'vitest';
Relevance

● Weak

Prior reviews rejected moving co-located tests into src/specs; convention enforcement not adopted.

PR-#1437
PR-#1442
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668281 requires tests to be placed under the corresponding src/specs/ directory
rather than being co-located with source. The added test file is located at
apps/self-hosted/src/styles/gallery-layout.test.ts, which is under src/.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

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

## Issue description
A new test file was added under `apps/self-hosted/src/` (`apps/self-hosted/src/styles/gallery-layout.test.ts`). Tests must not be co-located with source files and must live under the corresponding `src/specs/` tree.
## Issue Context
This test reads `apps/self-hosted/src/styles/themes/gallery.css` from disk. Moving the test will require updating the relative `join(HERE, ...)` path used to locate that CSS file.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


View low (5)
8. gallery-layout uses .test.ts 📜 Skill insight ⚙ Maintainability
Description
The new test file is named with the .test.ts pattern rather than the required
.spec.ts/.spec.tsx naming convention. This can prevent standard test tooling and conventions
from being applied consistently.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-2]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
Relevance

● Weak

Repo has rejected renaming .test.ts to .spec.ts in multiple reviews; naming convention not enforced.

PR-#1437
PR-#1463
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668286 requires tests to be named using .spec.ts or .spec.tsx. The added file
name is gallery-layout.test.ts, which does not match the required pattern.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

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 new test file uses the `.test.ts` naming pattern, but test files must follow the `.spec.ts` / `.spec.tsx` naming convention.
## Issue Context
After renaming, ensure any tooling references or imports (if any) still resolve, and update the filesystem-based CSS path logic if the file is also moved.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


9. gallery-layout test under src/ 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under apps/self-hosted/src/ instead of the required src/specs/
test hierarchy. This violates the repository test placement convention and can break test
discovery/maintenance expectations.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-4]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, it } from 'vitest';
Relevance

● Weak

Prior reviews rejected moving co-located tests into src/specs; convention enforcement not adopted.

PR-#1437
PR-#1442
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668281 requires tests to be placed under the corresponding src/specs/ directory
rather than being co-located with source. The added test file is located at
apps/self-hosted/src/styles/gallery-layout.test.ts, which is under src/.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

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

## Issue description
A new test file was added under `apps/self-hosted/src/` (`apps/self-hosted/src/styles/gallery-layout.test.ts`). Tests must not be co-located with source files and must live under the corresponding `src/specs/` tree.
## Issue Context
This test reads `apps/self-hosted/src/styles/themes/gallery.css` from disk. Moving the test will require updating the relative `join(HERE, ...)` path used to locate that CSS file.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


10. gallery-layout uses .test.ts 📜 Skill insight ⚙ Maintainability
Description
The new test file is named with the .test.ts pattern rather than the required
.spec.ts/.spec.tsx naming convention. This can prevent standard test tooling and conventions
from being applied consistently.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-2]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
Relevance

● Weak

Repo has rejected renaming .test.ts to .spec.ts in multiple reviews; naming convention not enforced.

PR-#1437
PR-#1463
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668286 requires tests to be named using .spec.ts or .spec.tsx. The added file
name is gallery-layout.test.ts, which does not match the required pattern.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test: Skill: add-test: Skill: add-test

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 new test file uses the `.test.ts` naming pattern, but test files must follow the `.spec.ts` / `.spec.tsx` naming convention.
## Issue Context
After renaming, ensure any tooling references or imports (if any) still resolve, and update the filesystem-based CSS path logic if the file is also moved.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


11. gallery-layout test under src/ 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under apps/self-hosted/src/ instead of the required src/specs/
test hierarchy. This violates the repository test placement convention and can break test
discovery/maintenance expectations.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-4]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, it } from 'vitest';
Relevance

● Weak

Prior reviews rejected moving co-located tests into src/specs; convention enforcement not adopted.

PR-#1437
PR-#1442
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668281 requires tests to be placed under the corresponding src/specs/ directory
rather than being co-located with source. The added test file is located at
apps/self-hosted/src/styles/gallery-layout.test.ts, which is under src/.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test

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

## Issue description
A new test file was added under `apps/self-hosted/src/` (`apps/self-hosted/src/styles/gallery-layout.test.ts`). Tests must not be co-located with source files and must live under the corresponding `src/specs/` tree.
## Issue Context
This test reads `apps/self-hosted/src/styles/themes/gallery.css` from disk. Moving the test will require updating the relative `join(HERE, ...)` path used to locate that CSS file.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


12. gallery-layout uses .test.ts 📜 Skill insight ⚙ Maintainability
Description
The new test file is named with the .test.ts pattern rather than the required
.spec.ts/.spec.tsx naming convention. This can prevent standard test tooling and conventions
from being applied consistently.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-2]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
Relevance

● Weak

Repo has rejected renaming .test.ts to .spec.ts in multiple reviews; naming convention not enforced.

PR-#1437
PR-#1463
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668286 requires tests to be named using .spec.ts or .spec.tsx. The added file
name is gallery-layout.test.ts, which does not match the required pattern.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test: Skill: add-test

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 new test file uses the `.test.ts` naming pattern, but test files must follow the `.spec.ts` / `.spec.tsx` naming convention.
## Issue Context
After renaming, ensure any tooling references or imports (if any) still resolve, and update the filesystem-based CSS path logic if the file is also moved.
## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

ⓘ 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

@qodo-code-review

qodo-code-review Bot commented Aug 13, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Self-hosted: add Gallery style template with image-grid archive layout

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a new "Gallery" template optimized for image-led blogs (grid archive, no sidebar).
• Introduce Gallery-specific components (tile card + null sidebar) while keeping the shared shell.
• Add CSS + guard tests to pin layout-critical selectors and keep token/test rosters in sync.
Diagram

graph TD
  A["Instance config\nstyleTemplate=gallery"] --> B["Theme registry\n(manifest)"] --> C["Component resolver\nresolveThemeComponents"] --> D["DefaultShell\n(shared frame)"] --> E["BlogPage\n.blog-page-measure"]
  C --> F["GalleryPostCard\n(tile)"]
  C --> G["GallerySidebar\n(null)"]
  H["themes/index.css"] --> I["gallery.css\n(tokens + layout rules)"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Hide sidebar purely with CSS
  • ➕ No new Sidebar component override needed
  • ➕ Fewer theme-manifest overrides
  • ➖ Default sidebar data-fetching would still run (wasted queries/CPU)
  • ➖ Harder to guarantee no side effects as sidebar grows features
2. Make BlogPage read --theme-content-width by default
  • ➕ Avoids adding a new class hook
  • ➕ Centralizes width behavior in one place
  • ➖ Would silently change the reading measure for all existing templates
  • ➖ Couples a text-centric wrapper to non-text layouts, reducing theme isolation
3. Implement Gallery as a full custom Shell (like Reader/Journal)
  • ➕ Maximum control over layout structure and responsive behavior
  • ➕ Could reduce reliance on CSS specificity tricks
  • ➖ More code/maintenance surface for a theme whose structure is intentionally small
  • ➖ Higher risk of diverging from shared shell improvements over time

Recommendation: Keep the PR’s approach: override Sidebar as a seam returning null (to avoid unnecessary queries) and use a minimal CSS-owned layout (grid + measure widening + sidebar column collapse) while retaining the shared shell. This keeps Gallery structurally distinct where needed without forking the entire frame, and the added guard test appropriately pins the load-bearing selectors/specificity.

Files changed (13) +414 / -6

Enhancement (9) +328 / -1
style-template-display.tsAdd Gallery template card metadata for template picker API +11/-0

Add Gallery template card metadata for template picker API

• Introduces a new 'gallery' entry in 'STYLE_TEMPLATE_DISPLAY' with name, tagline, swatch colors, and heading style so '/v1/templates' can advertise the new template.

apps/self-hosted/hosting/api/src/style-template-display.ts

style-templates.tsRegister gallery in the style template roster +1/-0

Register gallery in the style template roster

• Adds 'gallery' to 'STYLE_TEMPLATES', making it a valid 'StyleTemplate' id across hosting API and SPA theme resolution.

apps/self-hosted/hosting/api/src/style-templates.ts

i18n-strings.tsAdd editor label string for the Gallery template option +3/-0

Add editor label string for the Gallery template option

• Extends 'TranslationKey' and English translations with the Gallery option label used in the configuration UI.

apps/self-hosted/src/core/i18n-strings.ts

blog-page.tsxAdd blog-page-measure class as a theme-specific width hook +11/-1

Add blog-page-measure class as a theme-specific width hook

• Keeps the existing 'max-w-3xl' reading measure but adds 'blog-page-measure' so themes like Gallery can widen this wrapper via their own CSS without affecting other templates.

apps/self-hosted/src/features/blog/layout/blog-page.tsx

config-fields.tsExpose Gallery in style template label mapping +1/-0

Expose Gallery in style template label mapping

• Adds the 'gallery' translation key mapping so the configuration panel can render the Gallery template option correctly.

apps/self-hosted/src/features/floating-menu/config-fields.ts

gallery.cssAdd Gallery theme tokens plus grid layout + sidebar collapse rules +166/-0

Add Gallery theme tokens plus grid layout + sidebar collapse rules

• Defines Gallery light/dark palettes, typography, and layout tokens, then implements the layout with ':root[data-style-template="gallery"]' rules for measure widening, archive grid, and removing the sidebar column.

apps/self-hosted/src/styles/themes/gallery.css

gallery-post-card.tsxAdd GalleryPostCard tile optimized for image-led archives +109/-0

Add GalleryPostCard tile optimized for image-led archives

• Implements an image-forward tile with responsive 'sizes'/'srcSet' and a caption (title/date). Adds a no-image fallback panel using description/body summary to avoid holes in the grid.

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx

gallery-sidebar.tsxAdd GallerySidebar seam override that renders nothing +13/-0

Add GallerySidebar seam override that renders nothing

• Returns 'null' to eliminate sidebar rendering and prevent default sidebar queries (followers/following/chain info) from running for a layout that shows no sidebar.

apps/self-hosted/src/themes/gallery/gallery-sidebar.tsx

registry.tsRegister Gallery manifest (PostCard + Sidebar overrides) +13/-0

Register Gallery manifest (PostCard + Sidebar overrides)

• Adds a 'gallery' theme manifest with 'PostCard' and 'Sidebar' overrides and marks 'sidebar'/'listType' as unsupported options for this layout.

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

Tests (3) +85 / -5
gallery-layout.test.tsAdd guard tests for Gallery’s load-bearing layout CSS rules +64/-0

Add guard tests for Gallery’s load-bearing layout CSS rules

• Pins the three critical layout rules (archive grid, widened measure, sidebar collapse) and asserts selectors remain ':root'-rooted so they outrank 'components.css' despite import order.

apps/self-hosted/src/styles/gallery-layout.test.ts

theme-appearance-tokens.test.tsUpdate token guard expectations for the eighth template +4/-4

Update token guard expectations for the eighth template

• Adjusts palette/card-treatment guard counts to account for the new Gallery theme blocks.

apps/self-hosted/src/styles/theme-appearance-tokens.test.ts

registry.test.tsExtend theme resolution tests for Gallery’s seam overrides +17/-1

Extend theme resolution tests for Gallery’s seam overrides

• Treats Gallery as a layout-level theme and asserts it overrides 'PostCard' and 'Sidebar' while keeping the shared 'Shell', 'ArchiveList', and 'Navigation'.

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

Other (1) +1 / -0
index.cssImport gallery.css into the theme bundle +1/-0

Import gallery.css into the theme bundle

• Adds '@import "./gallery.css";' so Gallery tokens and layout rules are included in the built stylesheet.

apps/self-hosted/src/styles/themes/index.css

@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: 80d29516a9

ℹ️ 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".

Comment on lines +146 to +147
:root[data-style-template="gallery"] .blog-page-measure {
max-width: var(--theme-content-width);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep article pages at the reading measure

When Gallery is active, this selector widens every BlogPage, not only the archive: the post, search, and About routes all render through DefaultShell, and BlogPostBody explicitly uses max-w-none. Consequently, opening an article on a desktop expands its text from the existing max-w-3xl reading measure to 1200px, producing very long lines. Scope the widening to the archive/search grid rather than the shared page wrapper.

Useful? React with 👍 / 👎.

Comment on lines +164 to +165
:root[data-style-template="gallery"] .blog-sidebar-container {
display: none;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid hiding direct editor sidebars with archive CSS

On Gallery instances, this global selector also matches the /publish and /edit/$author/$permlink layouts, which use blog-sidebar-container but directly render BlogSidebar rather than the theme's null Sidebar seam. The sidebar therefore disappears while its account/community hooks and useQuery calls still mount and issue the requests this theme is intended to avoid. Scope this rule to DefaultShell or make those routes resolve the theme sidebar seam.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 85 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: 0f0d04e9-b011-4d81-9a62-3380663e4e5e

📥 Commits

Reviewing files that changed from the base of the PR and between 80d2951 and 7003f23.

📒 Files selected for processing (3)
  • apps/self-hosted/src/styles/gallery-layout.test.ts
  • apps/self-hosted/src/styles/themes/gallery.css
  • apps/self-hosted/src/themes/gallery/gallery-post-card.tsx

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 08d1c1ee-b584-49cb-b710-fbc11fa6464a

📥 Commits

Reviewing files that changed from the base of the PR and between 880007b and 80d2951.

📒 Files selected for processing (13)
  • apps/self-hosted/hosting/api/src/style-template-display.ts
  • apps/self-hosted/hosting/api/src/style-templates.ts
  • apps/self-hosted/src/core/i18n-strings.ts
  • apps/self-hosted/src/features/blog/layout/blog-page.tsx
  • apps/self-hosted/src/features/floating-menu/config-fields.ts
  • apps/self-hosted/src/styles/gallery-layout.test.ts
  • apps/self-hosted/src/styles/theme-appearance-tokens.test.ts
  • apps/self-hosted/src/styles/themes/gallery.css
  • apps/self-hosted/src/styles/themes/index.css
  • apps/self-hosted/src/themes/gallery/gallery-post-card.tsx
  • apps/self-hosted/src/themes/gallery/gallery-sidebar.tsx
  • apps/self-hosted/src/themes/registry.test.ts
  • apps/self-hosted/src/themes/registry.ts

📝 Walkthrough

Walkthrough

The PR adds the Gallery style template, configuration label, theme stylesheet, image-grid layout, custom post card, sidebar override, registry entries, and test coverage.

Changes

Gallery template registration and configuration

Layer / File(s) Summary
Template catalog and configuration
apps/self-hosted/hosting/api/src/style-templates.ts, apps/self-hosted/hosting/api/src/style-template-display.ts, apps/self-hosted/src/core/i18n-strings.ts, apps/self-hosted/src/features/floating-menu/config-fields.ts
The Gallery template is added to the roster, display metadata, translation types, English translations, and configuration label mapping.

Gallery styling and layout

Layer / File(s) Summary
Gallery styling and page layout
apps/self-hosted/src/styles/themes/gallery.css, apps/self-hosted/src/styles/themes/index.css, apps/self-hosted/src/features/blog/layout/blog-page.tsx
The Gallery theme defines light and dark tokens, widens the page measure, uses a responsive image grid, collapses the shell to one column, and hides the sidebar.

Gallery components and registry

Layer / File(s) Summary
Gallery components and registry
apps/self-hosted/src/themes/gallery/*, apps/self-hosted/src/themes/registry.ts
The registry selects GalleryPostCard and GallerySidebar. Cards render responsive cover images or text fallbacks, and the sidebar returns null.

Validation

Layer / File(s) Summary
Gallery layout validation
apps/self-hosted/src/styles/gallery-layout.test.ts, apps/self-hosted/src/styles/theme-appearance-tokens.test.ts, apps/self-hosted/src/themes/registry.test.ts
Tests validate Gallery CSS rules, updated template counts, and custom component resolution.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: ⚪ Minimal · up to 80d29

The Gallery layout and related registry, styling, and rendering changes are merge-ready after normal checks and review; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant BlogPage
  participant ArchiveList
  participant GalleryPostCard
  participant GallerySidebar
  BlogPage->>ArchiveList: render archive
  ArchiveList->>GalleryPostCard: provide post entry
  GalleryPostCard->>GalleryPostCard: resolve image or fallback content
  BlogPage->>GallerySidebar: resolve sidebar component
  GallerySidebar-->>BlogPage: return null
Loading

Possibly related PRs

Poem

I’m a rabbit hopping through the grid,
Where every bright image gets a lid.
No sidebar thumps, the cards align,
Fallback words keep blank posts fine.
Gallery blooms in rows of light. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a Gallery layout for image-led self-hosted blogs.
Linked Issues check ✅ Passed The changes implement the Gallery roster, grid layout, sidebar removal, fallback cards, editor integration, theme tokens, and guard tests required by issue [#1467].
Out of Scope Changes check ✅ Passed All changes support the Gallery layout objectives in issue [#1467], including implementation, integration, styling, and targeted tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/self-hosted-gallery-theme

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.

@feruzm

feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Correction to the PR body: I cannot attach images through the API, so here are the measured results instead. Screenshots are on the build box and can be added to the PR by hand.

Served the real built dist through the published image's nginx with a config for an image-heavy account:

viewport columns tiles sidebar horizontal scroll console
1440px 4 21 collapsed none clean
390px 1 21 collapsed none clean

Dark config confirmed separately: data-theme=dark, ground rgb(18, 18, 18) and caption colour both resolving from the Gallery dark block rather than inherited.

Worth recording for whoever reviews: the archive width needed the blog-page-measure hook because the shared BlogPage caps at max-w-3xl. Before that, Gallery rendered 2 columns at 1440px instead of 4, which looked like a deliberate layout rather than a constraint.

@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 (2)

Grey Divider


Remediation recommended

1. Mismatched img sizes hint ✓ Resolved 🐞 Bug ➹ Performance
Description
GalleryPostCard hard-codes the <img sizes> hint even though Gallery uses a content-width-capped
auto-fill grid, so the hint often overestimates tile width and makes browsers download larger
srcset candidates than necessary. This reintroduces the sizing-drift risk the existing grid sizing
helper was designed to avoid.
Code

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[R67-70]

+              srcSet={buildSrcSet(imageUrl) || undefined}
+              /* One tile wide: the grid packs at a 260px minimum and grows,
+                 so a third of a wide window is the realistic upper bound. */
+              sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
Relevance

●●● Strong

Hard-coded img sizes drift was previously flagged and accepted; team prefers deriving sizes from
actual grid/layout.

PR-#1463

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Gallery layout uses an auto-fill grid (repeat(auto-fill, minmax(...))) and caps the page
width via --theme-content-width, so a fixed 33vw assumption can substantially overestimate the
actual tile width on wide viewports. The codebase already has a helper (computeThemeGridSizes)
used by the default post card to avoid hard-coded sizes strings.

apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
apps/self-hosted/src/styles/themes/gallery.css[82-95]
apps/self-hosted/src/styles/themes/gallery.css[146-155]
apps/self-hosted/src/themes/grid-sizes.ts[1-31]
apps/self-hosted/src/features/blog/components/blog-post-item.tsx[93-112]
PR-#1463

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

## Issue description
`GalleryPostCard` uses a hard-coded `sizes` attribute (`50vw`/`33vw`), but the Gallery archive layout is an `auto-fill` grid inside a wrapper capped at `--theme-content-width`. On wide screens this causes the browser to assume a much larger rendered image width than reality and fetch oversized `srcset` candidates.

## Issue Context
The codebase already introduced a theme-aware sizing mechanism (`useThemeGridSizes` / `computeThemeGridSizes`) to avoid hard-coded column assumptions drifting from theme/layout reality.

## Fix approach
Choose one of these (in descending preference):
1. **Make `sizes` reflect Gallery’s real layout contract** by expressing it in CSS terms using `min()`/`calc()` and the theme variables you already define (e.g., `--theme-content-width`, `--theme-layout-container-padding`, `--theme-grid-gap`) so it accounts for the content-width cap.
2. If you want to reuse existing infrastructure, **switch GalleryPostCard to use `useThemeGridSizes()`** (and consider whether `computeThemeGridSizes` should be extended to better model content-width caps / auto-fill layouts).

Also consider memoizing `srcSet` like `BlogPostItem` does, to avoid recomputing it on re-renders.

## Fix Focus Areas
- apps/self-hosted/src/themes/gallery/gallery-post-card.tsx[63-74]
- apps/self-hosted/src/styles/themes/gallery.css[82-95]
- apps/self-hosted/src/styles/themes/gallery.css[146-155]
- apps/self-hosted/src/themes/grid-sizes.ts[15-31]

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



Informational

2. gallery-layout test under src/ 📜 Skill insight ⌂ Architecture
Description
The new test file is co-located under apps/self-hosted/src/ instead of the required src/specs/
test hierarchy. This violates the repository test placement convention and can break test
discovery/maintenance expectations.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-4]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { describe, expect, it } from 'vitest';
Relevance

● Weak

Prior reviews rejected moving co-located tests into src/specs; convention enforcement not adopted.

PR-#1437
PR-#1442
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668281 requires tests to be placed under the corresponding src/specs/ directory
rather than being co-located with source. The added test file is located at
apps/self-hosted/src/styles/gallery-layout.test.ts, which is under src/.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test

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

## Issue description
A new test file was added under `apps/self-hosted/src/` (`apps/self-hosted/src/styles/gallery-layout.test.ts`). Tests must not be co-located with source files and must live under the corresponding `src/specs/` tree.

## Issue Context
This test reads `apps/self-hosted/src/styles/themes/gallery.css` from disk. Moving the test will require updating the relative `join(HERE, ...)` path used to locate that CSS file.

## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

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


3. gallery-layout uses .test.ts 📜 Skill insight ⚙ Maintainability
Description
The new test file is named with the .test.ts pattern rather than the required
.spec.ts/.spec.tsx naming convention. This can prevent standard test tooling and conventions
from being applied consistently.
Code

apps/self-hosted/src/styles/gallery-layout.test.ts[R1-2]

+import { readFileSync } from 'node:fs';
+import { dirname, join } from 'node:path';
Relevance

● Weak

Repo has rejected renaming .test.ts to .spec.ts in multiple reviews; naming convention not enforced.

PR-#1437
PR-#1463
PR-#1441

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2668286 requires tests to be named using .spec.ts or .spec.tsx. The added file
name is gallery-layout.test.ts, which does not match the required pattern.

apps/self-hosted/src/styles/gallery-layout.test.ts[1-4]
Skill: add-test

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 new test file uses the `.test.ts` naming pattern, but test files must follow the `.spec.ts` / `.spec.tsx` naming convention.

## Issue Context
After renaming, ensure any tooling references or imports (if any) still resolve, and update the filesystem-based CSS path logic if the file is also moved.

## Fix Focus Areas
- apps/self-hosted/src/styles/gallery-layout.test.ts[1-64]

ⓘ 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 17/18, lines 420/200; both must reach the floor). Router rationale: This adds a new theme across registry, rendering components, shared layout styling, CSS tokens, configuration/API catalogs, and tests, creating multiple independent paths with easy-to-miss integration defects.

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/gallery/gallery-post-card.tsx Outdated
@feruzm

feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

All three fixed at 53bab1a, and the second one had a fourth instance the review did not name.

Article and About pages lost their reading measure. Valid, and the worst of the three: I only ever looked at the archive. .blog-page-measure wraps every route in the shell, so prose was rendering at the gallery's 1200px. The rule is now \.blog-page-measure:has(.blog-posts-list), so it widens only a page that actually contains a grid. That covers the archive and search results (both render .blog-posts-list) and leaves articles and About at 768px.

The sidebar rules reached /publish and /edit. Valid. Both routes render .blog-sidebar-container themselves, outside the theme seam. Worth noting the same reach applied to my \.blog-layout-grid { grid-template-columns: 1fr } rule, which would have collapsed the editor's sidebar column as well, so on a Gallery instance the editor lost its sidebar twice over. Both rules are now qualified by :has(.blog-page-measure), the wrapper only the blog shell renders.

Verified by building the app and constructing both route shapes against the real stylesheet:

shape sidebar grid columns
blog shell (has .blog-page-measure) display: none 1280px (single)
publish/edit (no measure wrapper) display: block 968px 280px (sidebar intact)

And per route, live: archive 1200px wide with 4 columns, article 768px, About 768px.

Over-fetching. Valid, and embarrassing given #1463 fixed exactly this class of bug. 33vw claimed 475px at 1440 for a tile that is 285px, because the grid sits inside a 1200px cap so the tile stops growing while the viewport does not. Now (max-width: 639px) 100vw, (max-width: 1023px) 360px, 300px, taken from measured widths. The browser now picks the 320px candidate for a 285px tile.

The guard test was rewritten around these two scoping bugs rather than just the rules' existence, so a future edit that unscopes them fails rather than silently taking the editor's sidebar away again.

@feruzm

feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Fixed at d8ddfbe, and measuring it changed the answer.

You are right that the previous hint diverged, and the two-column case you name is the worst of it. But the suggested breakpoints of roughly 580, 860 and 1140 assume the container grows continuously with the viewport. It does not: Tailwind's container snaps to breakpoint max-widths, so the tile width steps, and the steps land in different places. Measured at 1x from the rendered grid:

viewport container columns tile
< 580 100vw - 40 1 up to 535
580 to 767 100vw - 40 2 260 to 354
768 to 1023 728 (snapped) 2 354
1024 to 1279 984 (snapped) 3 315
1280+ 1200 (capped) 4 285

So the transitions are at 580, 768, 1024 and 1280, and three of the five ranges are a fixed pixel width rather than anything expressible as vw. The hint is now:

(max-width: 579px) calc(100vw - 40px),
(max-width: 767px) calc((100vw - 60px) / 2),
(max-width: 1023px) 354px,
(max-width: 1279px) 315px,
285px

Verified by loading in a fresh context per viewport, since a cached larger candidate is kept and would otherwise make every reading after the first a copy of the first:

viewport tile chosen ratio
390 350 600 1.71x
575 535 600 1.12x
600 270 320 1.19x
700 290 320 1.10x
900 354 600 1.69x
1100 315 320 1.02x
1280 285 320 1.12x
1600 285 320 1.12x

No under-fetch anywhere, and the common desktop case drops from 600 to 320. The two remaining 1.7x rows are not the hint's doing: buildSrcSet offers 320, 600, 800, 1024, 1280, so any tile between 321 and 600 has to take 600. Narrowing that ladder is a render-helper change and out of scope here.

@feruzm

feruzm commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Correct, fixed at 7003f23. And my previous comment's table was wrong: it said the 580 to 767 range grows to 354, when my own measurements in that same run showed 700px rendering a 290px tile. I misread my own data and published it as a table, which is worse than not having measured. The container caps at 640 there exactly as you say.

Measured densely across the range this time:

viewport container columns tile
580, 600, 620, 639 540, 560, 580, 599 2 260, 270, 280, 290
640 to 767 600 (capped) 2 290, flat
768 to 1023 728 2 354
1024 to 1279 984 3 315
1280+ 1200 4 285

The formula now stops at 639 and hands over to a flat 290 through 767, which dovetails exactly: at 639 the calculation yields 289.5.

Verified at both pixel ratios with a fresh context per width:

viewport tile 1x chosen 2x chosen
600 270 320 600
640 290 320 (was 600) 600 (was 800)
700 290 320 600
767 290 320 600
1100 315 320 800
1600 285 320 600

Both selections you predicted are what changed. No under-fetch at either ratio anywhere in the range; at 2x every row is now between 1.03x and 1.27x of what is displayed.

Two rows still sit near 1.7x at 1x (390 and 768 to 1023), and those are the candidate ladder rather than the hint: buildSrcSet offers 320, 600, 800, 1024, 1280, so a 350 or 354px tile has to take 600. Out of scope here, and worth a render-helper issue if it matters.

@feruzm
feruzm merged commit a2a4403 into develop Aug 13, 2026
12 checks passed
@feruzm
feruzm deleted the feature/self-hosted-gallery-theme branch August 13, 2026 10:10
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: add a Gallery layout for image-led blogs

1 participant