Skip to content

feat(projects): filter the overview by audience and platform - #519

Merged
TheMeinerLP merged 1 commit into
mainfrom
feat/project-filters
Oct 11, 2026
Merged

TheMeinerLP merged 1 commit into
mainfrom
feat/project-filters

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Collaborator

Summary

The projects overview (/[locale]/projects) can now be filtered by audience (all, public, internal) and by platform. Platform is a multi-select over the platforms the projects actually name, with a count per platform. The filter state lives in the query string, so a filtered view can be shared and survives a reload.

  • ?audience=internal&platform=Minestom,Paper, written with router.replace, so filter changes do not add history entries.
  • Platforms combine as "any of": a project matches when it runs on at least one selected platform. Audience and platforms combine as "and".
  • The result count is a polite live region. A "Filter zurücksetzen" action appears only while a filter is active, and an empty state explains a combination that matches nothing.
  • Grouping is unchanged: with audience "all" both sections show; with one audience only that section renders, because the other group is empty and already hidden.

Design decisions

  • The server does not read the query. The brief asked for server-side rendering of the filtered list. The repository forbids it: /en/** and /de/** are edge-cached, and tests/architecture/request-independent-render.spec.ts bans route.query, cookies and request headers in every render. So the server renders all projects and the browser applies the URL after mount. Consequence: on a reload of a filtered URL the full list paints for a moment before the filter applies. Changing this means revisiting the edge-cache design, not this page, and was deliberately not done here.
  • Facets are global. Platform counts come from all projects, not from the current audience, so chips do not appear or disappear when the audience changes.
  • Logic is pure and lives in the layer. layers/projects/utils/projectFilter.ts holds filterProjects, platformFacets, togglePlatform, parseProjectFilter and serializeProjectFilter. Invalid audiences and platforms that no project names are dropped on parse. The component ProjectFilters.vue is controlled: it renders from props and emits the next filter. The page owns the URL.
  • Platform sort uses an explicit en locale so the server and the browser order the chips identically and hydration does not disagree.

SEO

  • Canonical: unchanged. usePageSeo strips the query, so /de/projects?audience=internal canonicalises to /de/projects. Verified against the preview build: the filtered URL serves a canonical of https://onelitefeather.net/de/projects.
  • ItemList JSON-LD: still lists all 17 projects in the unfiltered order, whatever the filter is. The page builds it from the unfiltered groups.
  • Sitemap: no filtered URLs are added. The sitemap is generated from content, and nothing in this change touches it.

Tests

  • tests/projects/project-filter.spec.ts: pure logic (filtering, facets, parse, serialize, toggle, active state), Node environment.
  • tests/projects/project-filters.spec.ts: component test in the Nuxt environment. Chip toggling, aria-pressed, counts, the live count and the reset action.
  • tests/projects/pages.spec.ts: overview-only checks. ItemList built from all projects, router.replace and never router.push, URL read only after mount.

Local results: full pnpm test 187 files / 1708 tests passing; pnpm quality at baseline (186 ESLint errors, 17 warnings, 3 TypeScript errors, no regression); pnpm build succeeds.

Browser check against the preview build at 360 px: scrollWidth equals clientWidth (no horizontal scroll). Chip clicks update the URL in the same document. Reset clears the query. A combination with no matches shows the empty state with the count "0 von 17 Projekten".

Platform facets (current content)

platform projects
CloudNet 1
Docker 2
Folia 1
GitHub Actions 1
JVM 1
Kubernetes 2
Micronaut 1
Minestom 9
Paper 4
Renovate 1
Velocity 1

https://claude.ai/code/session_019zws4ceJu2jctk3XT48fMX

The projects overview gets an audience filter (all, public, internal) and
a multi-select platform filter built from the platforms the projects name,
with counts. The state lives in the query string (?audience=&platform=,
written with router.replace), and filtering keeps the grouped sections.

The server renders every project; the browser applies the URL after mount,
because the edge-cached HTML must not depend on the request.

Claude-Session: https://claude.ai/code/session_019zws4ceJu2jctk3XT48fMX
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
launchpad ff55114 Commit Preview URL

Branch Preview URL
Oct 11 2026, 03:45 PM

@TheMeinerLP
TheMeinerLP merged commit 50f0c0e into main Oct 11, 2026
8 checks passed
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.

1 participant