Collection Listing Templating Implementation Plan
Collection Listing Templating Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Let a page declare a folder-backed collection in frontmatter and render custom cards for it using a small loop block around normal HTML.
Architecture: Reuse the existing folder-query logic behind List, move it into a shared server-side collection loader, resolve named collections from page frontmatter before rendering, and add a minimal loop/interpolation layer in the page body. Implement the selected syntax in the existing Flowershow pipeline; do not broaden scope into general Markdoc adoption. No pagination in v1.
Tech Stack: Next.js App Router, next-mdx-remote-client, unified/remark, React 19, Prisma, tRPC, Vitest, Playwright
Implementation Handoff
- Related issue: #1222
- Primary design doc: 2026-03-29-collection-listing-templating-design.md
- Tutorial draft: custom-collection-cards.md
- Reuse the existing folder-query behavior behind
List; do not invent a second collection-query path. - Keep scope tight:
- frontmatter-defined collections
- documented collection name
items - folder-based source only
sortBy: date | titlesortDirection: asc | desc{% for item in items %}and{{ item.field }}only- no pagination
- no conditionals
- no nested loops
- must work in
.md, not just.mdx
- If the user wants pagination or a standard index page, the answer remains
List.
Target Authoring Model
---
title: Projects
collections:
items:
from: /projects
sortBy: date
sortDirection: desc
---
{% for item in items %}
<article class="project-card">
<a href="{{ item.url }}">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</a>
</article>
{% /for %}
Scope
- v1 supports frontmatter-defined named collections
- v1 supports folder-based collections only
- v1 supports
sortBy: date | title - v1 supports
sortDirection: asc | desc - v1 supports loop + interpolation only
- docs and examples use a single collection name:
items - v1 must work in normal
.mdpages - v1 does not support pagination
- v1 does not replace
List
Task 1: Align docs and fixtures to the selected model
Files:
- Modify:
content/flowershow-app/docs/custom-collection-cards.md - Modify:
docs/plans/2026-03-29-collection-listing-templating-design.md - Create:
apps/flowershow/e2e/fixtures/test-site/projects/README.md - Create:
apps/flowershow/e2e/fixtures/test-site/projects/alpha.md - Create:
apps/flowershow/e2e/fixtures/test-site/projects/beta.md - Create:
apps/flowershow/e2e/fixtures/test-site/projects/gamma.md
Step 1: Add the fixture content
Create a realistic /projects folder with frontmatter fields used by the tutorial:
titledescriptiondateimagestatus
Step 2: Add the example page
Create projects/README.md using:
---
title: Projects
collections:
items:
from: /projects
sortBy: date
sortDirection: desc
---
{% for item in items %}
<article class="project-card">
<a href="{{ item.url }}">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</a>
</article>
{% /for %}
Step 3: Commit
git add content/flowershow-app/docs/custom-collection-cards.md docs/plans/2026-03-29-collection-listing-templating-design.md apps/flowershow/e2e/fixtures/test-site/projects
git commit -m "docs: align collection cards examples with frontmatter model"
Task 2: Extract the current List query into a shared collection loader
Files:
- Create:
apps/flowershow/lib/collections.ts - Create:
apps/flowershow/lib/collections.test.ts - Modify:
apps/flowershow/server/api/routers/site.ts
Step 1: Write the failing unit tests
Cover:
- folder inclusion
README.md/index.mdexclusiondate DESCorderingtitle ASCfallback- permalink-vs-appPath URL selection
- image/wiki-link resolution
Step 2: Run the test and confirm failure
Run:
pnpm --filter @flowershow/app test:unit -- lib/collections.test.ts
Step 3: Implement the loader
Create a shared function with a shape like:
getCollectionItems({
db,
site,
siteId,
dir,
sortBy, // v1: date | title
sortDirection, // v1: asc | desc
mediaField,
})
Return:
{
items: Array<{
url: string;
path: string;
metadata: PageMetadata | null;
}>
}
Step 4: Reuse it from getListComponentItems
Replace the router-local logic in site.ts.
Step 5: Re-run tests
Run:
pnpm --filter @flowershow/app test:unit -- lib/collections.test.ts components/public/mdx/list.test.tsx
Step 6: Commit
git add apps/flowershow/lib/collections.ts apps/flowershow/lib/collections.test.ts apps/flowershow/server/api/routers/site.ts
git commit -m "refactor: extract shared collection loader"
Task 3: Add typed frontmatter support for named collections
Files:
- Modify:
apps/flowershow/server/api/types.ts - Create:
apps/flowershow/lib/collections-config.ts - Create:
apps/flowershow/lib/collections-config.test.ts
Step 1: Write the failing unit tests
Cover:
- valid
collections.items.from - optional
sortBy - optional
sortDirection - invalid empty
from - invalid unknown direction
Step 2: Run the test and confirm failure
Run:
pnpm --filter @flowershow/app test:unit -- lib/collections-config.test.ts
Step 3: Implement minimal config typing
Define:
type PageCollectionConfig = {
from: string;
sortBy?: 'date' | 'title';
sortDirection?: 'asc' | 'desc';
};
And add:
collections?: Record<string, PageCollectionConfig>;
to PageMetadata.
In docs and fixtures, only use items.
Step 4: Re-run tests
Run:
pnpm --filter @flowershow/app test:unit -- lib/collections-config.test.ts
Step 5: Commit
git add apps/flowershow/server/api/types.ts apps/flowershow/lib/collections-config.ts apps/flowershow/lib/collections-config.test.ts
git commit -m "feat: add frontmatter collection config"
Task 4: Resolve frontmatter collections before page rendering
Files:
- Modify:
apps/flowershow/app/(public)/site/[user]/[project]/[[...slug]]/page.tsx - Modify:
apps/flowershow/lib/markdown.ts - Create:
apps/flowershow/lib/resolve-page-collections.ts - Create:
apps/flowershow/lib/resolve-page-collections.test.ts
Step 1: Write the failing unit tests
Cover:
- a page with
collections.items - multiple named collections
- empty collections
- missing
from
Step 2: Run the test and confirm failure
Run:
pnpm --filter @flowershow/app test:unit -- lib/resolve-page-collections.test.ts
Step 3: Implement collection resolution
In the page route:
- read
metadata.collections - resolve each named collection through the shared loader
- build a render-time context such as:
{
items: [...]
}
Pass that context into the markdown/MDX pipeline.
Do not add pagination support here.
Step 4: Re-run tests
Run:
pnpm --filter @flowershow/app test:unit -- lib/resolve-page-collections.test.ts
Step 5: Commit
git add apps/flowershow/app/(public)/site/[user]/[project]/[[...slug]]/page.tsx apps/flowershow/lib/markdown.ts apps/flowershow/lib/resolve-page-collections.ts apps/flowershow/lib/resolve-page-collections.test.ts
git commit -m "feat: resolve frontmatter collections before render"
Task 5: Add the minimal loop/interpolation syntax in the existing pipeline
Files:
- Create:
apps/flowershow/lib/remark-collection-loop.ts - Create:
apps/flowershow/lib/remark-collection-loop.test.ts - Create:
apps/flowershow/lib/collection-renderer.ts - Create:
apps/flowershow/lib/collection-renderer.test.ts - Modify:
apps/flowershow/lib/markdown.ts
Step 1: Write the failing tests
Cover:
{% for item in items %}loop expansion{{ item.title }}interpolation- empty values render as empty strings
- arrays join with
, - HTML escaping
- unknown collection name
Step 2: Run the tests and confirm failure
Run:
pnpm --filter @flowershow/app test:unit -- lib/collection-renderer.test.ts lib/remark-collection-loop.test.ts
Step 3: Implement the narrow feature
Support only:
{% for item in items %} ... {% /for %}{{ item.field }}
Do not add:
- conditionals
- nested loops
- expressions
- pagination
- arbitrary loop variable names
- arbitrary collection names in docs examples beyond
items
Step 4: Register it in both render paths
Update processMarkdown(...) and getMdxOptions(...) in markdown.ts.
Run this loop/interpolation pass before downstream HTML/rehype transforms that would otherwise consume or mangle the markers.
Step 5: Re-run tests
Run:
pnpm --filter @flowershow/app test:unit -- lib/collection-renderer.test.ts lib/remark-collection-loop.test.ts
Step 6: Commit
git add apps/flowershow/lib/remark-collection-loop.ts apps/flowershow/lib/remark-collection-loop.test.ts apps/flowershow/lib/collection-renderer.ts apps/flowershow/lib/collection-renderer.test.ts apps/flowershow/lib/markdown.ts
git commit -m "feat: add collection loop rendering"
Task 6: Prove the feature end-to-end
Files:
- Create:
apps/flowershow/e2e/specs/custom-collection-cards.spec.ts - Modify:
apps/flowershow/e2e/specs/blog.spec.ts(only if needed)
Step 1: Write the failing e2e test
Cover:
/projectspage renders cards from frontmatter-defineditems- cards are ordered by date descending
- title/description/url render correctly
- no pagination UI appears
- the same feature works in a normal
.mdpage
Step 2: Run the focused e2e test and confirm failure
Run:
pnpm --filter @flowershow/app test:e2e -- custom-collection-cards.spec.ts
Step 3: Fix any missing integration details
Only fix what the test exposes.
Step 4: Re-run the e2e test
Run:
pnpm --filter @flowershow/app test:e2e -- custom-collection-cards.spec.ts
Step 5: Commit
git add apps/flowershow/e2e/specs/custom-collection-cards.spec.ts apps/flowershow/e2e/fixtures/test-site/projects
git commit -m "test: cover custom collection cards e2e"
Task 7: Final docs pass
Files:
- Modify:
content/flowershow-app/docs/custom-collection-cards.md - Modify:
content/flowershow-app/docs/list-component.md - Modify:
content/flowershow-app/docs/syntax-mode.md - Create:
content/flowershow-app/changelog/2026-03-29-custom-collection-cards-alpha.md
Step 1: Update docs to match reality
Required points:
- collections are defined in frontmatter
- rendering uses the loop block in the page body
- no pagination in v1
- use
Listwhen you want pagination
Step 2: Commit
git add content/flowershow-app/docs/custom-collection-cards.md content/flowershow-app/docs/list-component.md content/flowershow-app/docs/syntax-mode.md content/flowershow-app/changelog/2026-03-29-custom-collection-cards-alpha.md
git commit -m "docs: document custom collection cards"
Task 8: Verification
Step 1: Run unit tests
pnpm --filter @flowershow/app test:unit -- lib/collections.test.ts lib/collections-config.test.ts lib/resolve-page-collections.test.ts lib/collection-renderer.test.ts lib/remark-collection-loop.test.ts components/public/mdx/list.test.tsx
Step 2: Run e2e
pnpm --filter @flowershow/app test:e2e -- custom-collection-cards.spec.ts
Step 3: Run lint
pnpm --filter @flowershow/app lint
Step 4: Final commit
git add apps/flowershow content/flowershow-app docs/plans/2026-03-29-collection-listing-templating-implementation.md
git commit -m "feat: add custom collection cards"
Appendix A: Short Justifications
- Frontmatter for collection binding: keeps data acquisition separate from rendering and fits Flowershow's current page metadata model.
- Reuse
Listquery logic: avoids duplicate folder-query behavior and keeps ordering/path rules consistent. - No pagination in v1:
pagination adds routing and UI complexity;
Listalready covers that use case. - Small loop syntax in the existing pipeline: keeps custom HTML in the page body and minimizes templating surface area without broadening scope into a new page-rendering system.
Appendix B: Reference Files
apps/flowershow/components/public/mdx/list.tsxapps/flowershow/server/api/routers/site.tsapps/flowershow/app/(public)/site/[user]/[project]/[[...slug]]/page.tsxapps/flowershow/lib/markdown.tsdocs/plans/2026-03-29-collection-listing-templating-design.mdcontent/flowershow-app/docs/custom-collection-cards.md