Skip to content

feat(catalog): sticky category quick-nav (icon jump chips)#549

Merged
rdmueller merged 2 commits into
LLM-Coding:mainfrom
raifdmueller:feat/category-quicknav
Jun 1, 2026
Merged

feat(catalog): sticky category quick-nav (icon jump chips)#549
rdmueller merged 2 commits into
LLM-Coding:mainfrom
raifdmueller:feat/category-quicknav

Conversation

@raifdmueller
Copy link
Copy Markdown
Contributor

@raifdmueller raifdmueller commented Jun 1, 2026

Problem

The catalog lists 150+ anchors across 15 categories on a single long page with no in-page wayfinding — you scroll through every section to reach one category.

Solution

A sticky quick-nav above the card grid: one icon chip per non-empty category with a live anchor count, jumping to that category's section.

  • Static-first: plain in-page anchors (#category-<id>). Works with JavaScript disabled; sections get a matching id + scroll-margin-top so the heading clears the sticky bar.
  • Compact, icon-only: a single horizontally-scrollable row keeps the sticky bar at ~40px instead of ~120px (the wrapped text version was 4 rows tall). The category name is the accessible label (title + aria-label); the icon is aria-hidden.
  • Unique icons for all 14 rendered categories — strategic-planning now ♟️ instead of a duplicate 🎯, plus ✍️ creative-writing and 🧠 knowledge-management (these also fix the section-heading fallback 📌).
  • Fixes a pre-existing bug: added the missing categories.creative-writing translation (EN/DE) — the heading was showing the raw i18n key.
  • Filter-aware: chip counts sync with the search/role filter, and emptied categories drop out of the nav.
  • Hidden below sm: on phones, search + role filter already cover navigation and the sticky strip would waste scarce vertical space.

Tests

5 new unit tests for the nav contract (jump link per non-empty category, section ids, counts, accessible icon label). All 96 unit tests pass; lint + prettier clean.

Verification (preview build)

  • Sticky bar height ~42px, single scrollable row, 14 chips.
  • Jump: scroll-margin-top lands the heading below the sticky bar (verified via scrollIntoView positioning).
  • Filter sync: search "gherkin" → 2 chips with counts 1/2; clearing restores all 14.
  • Dark mode: chip contrast OK.
  • Responsive: display:none < 640px, block ≥ 640px.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Neue Kategorie-Schnellnavigation (Sticky Quick‑Nav) mit Jump‑Links und Live‑Zählern pro Kategorie
    • Neue Kategorie "Kreatives Schreiben & Storytelling" ergänzt
    • Sanfte In‑Page‑Scrollanimation beim Springen zu Kategorien
  • Style

    • Layout- und Styles für die Quick‑Nav, responsive Darstellung und korrektes Scroll‑Offset hinzugefügt
  • Tests

    • Neue Tests zur Absicherung der Quick‑Nav, Zähler und Accessibility‑Attribute ergänzt

The catalog lists 150+ anchors across 15 categories on one long page
with no in-page wayfinding. Adds a sticky quick-nav above the card grid:
one icon chip per non-empty category with a live anchor count, linking
to that category section.

- Plain in-page anchors (#category-<id>) — works without JavaScript;
  sections get a matching id and scroll-margin-top so the heading clears
  the sticky bar.
- Icon-only chips in a single horizontally scrollable row keep the
  sticky bar short (~40px vs ~120px wrapped); the category name is the
  accessible label (title + aria-label), the icon is decorative.
- Assigned unique icons to all 14 rendered categories (strategic-planning
  ♟️ instead of a duplicate 🎯; added ✍️ creative-writing, 🧠
  knowledge-management) — also fixes the section-heading fallback icons.
- Added the missing categories.creative-writing translation (EN/DE); the
  heading previously showed the raw i18n key.
- Chip counts stay in sync with the search/role filter, and emptied
  categories drop out of the nav.
- Hidden below the sm breakpoint: on phones, search + role filter cover
  navigation and the sticky strip would waste vertical space.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 4f99867f-9ed7-4a9e-8e28-14d91698d633

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac798c and d9553a4.

📒 Files selected for processing (2)
  • website/src/components/card-grid.js
  • website/src/components/card-grid.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • website/src/components/card-grid.test.js

Walkthrough

Diese Pull Request ergänzt eine Kategorie-Schnellnavigation mit Jump-Links zum Card-Grid. Die CATEGORY_ICONS werden um neue Kategorien erweitert, eine Sticky-Navigation rendert Jump-Links für nicht-leere Kategorien mit Live-Zählern, Filter-Logik synchronisiert die Navigation basierend auf Kartensichtbarkeit, Styling implementiert das responsive Layout, und neue Tests sowie Übersetzungen dokumentieren die Funktionalität.

Changes

Kategorie-Schnellnavigation mit Jump-Links

Layer / File(s) Summary
Icon-Erweiterung und Schnellnavigation-Rendering
website/src/components/card-grid.js
CATEGORY_ICONS wird um creative-writing und knowledge-management ergänzt. renderCardGrid() generiert eine Sticky-Schnellnavigation mit Jump-Links über neue interne Helfer. renderCategorySection() erhält id="category-<...>" Attribute für zuverlässige Link-Navigation.
Filter-Synchronisierung der Schnellnavigation
website/src/components/card-grid.js
applyCardFilters() wird erweitert, um die Schnellnavigation zu synchronisieren: syncCategoryNav() blendet leere Kategorien aus und aktualisiert Live-Zähler basierend auf sichtbaren Karten pro Kategorie.
Schnellnavigation-Styling und Scroll-Verhalten
website/src/styles/main.css
.category-section erhält scroll-margin-top für korrekte Sprungpositionen. Neue Sticky-Navigation-Styles (.category-nav, .category-nav-link, .category-nav-icon, .category-nav-count) implementieren responsive horizontale Chip-Zeile. HTML erhält scroll-behavior: smooth; für glatte Übergänge.
Tests und Internationalisierung
website/src/components/card-grid.test.js, website/src/translations/{de,en}.json
Neue Vitest-Tests validieren Jump-Link-Rendering, Kategorie-Filterung und Accessibility-Attribute. Deutsche und englische Übersetzungen für nav.categoryJump und categories.creative-writing hinzugefügt.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • LLM-Coding/Semantic-Anchors#379: Diese PR führt die neue knowledge-management-Kategorie ein (über categories.json/anchors.json und i18n), die diese PR dann in der Card-Grid-UI sichtbar macht, indem CATEGORY_ICONS erweitert und die Kategorie-Schnellnavigation dafür generiert/synchronisiert wird.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Der Titel fasst die Hauptänderung prägnant zusammen: Hinzufügung einer Sticky-Kategorie-Schnellnavigation mit Icon-Chips für schnelle Navigation zwischen Kategorien.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
website/src/styles/main.css (1)

140-142: ⚡ Quick win

Smooth-Scroll sollte Reduced-Motion respektieren

scroll-behavior: smooth ist global aktiv, aber ohne Fallback für Nutzer mit reduzierter Bewegung. Bitte per prefers-reduced-motion: reduce auf auto zurückschalten.

Vorgeschlagener Fix
 html {
   scroll-behavior: smooth;
 }
+
+@media (prefers-reduced-motion: reduce) {
+  html {
+    scroll-behavior: auto;
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/src/styles/main.css` around lines 140 - 142, Die globale Regel "html
{ scroll-behavior: smooth; }" überschreibt Nutzerpräferenzen für reduzierte
Bewegung; fügen Sie eine CSS-Media-Query für "prefers-reduced-motion: reduce"
hinzu und setzen darin die Regel für den "html" Selektor auf "scroll-behavior:
auto" (ggf. mit !important) so dass Nutzer mit reduziertem Motion-Setting wieder
die Standard-Scrollverhalten erhalten.
website/src/components/card-grid.js (1)

504-509: ⚡ Quick win

Leere Quick-Nav komplett ausblenden, wenn alle Chips weggefiltert sind.

Aktuell werden nur einzelne <li>-Einträge versteckt. Wenn alle Kategorien leer sind, bleibt ein leerer sticky Container sichtbar.

🔧 Vorschlag
 function syncCategoryNav() {
+  let visibleItems = 0
   document.querySelectorAll('.category-nav-link').forEach((link) => {
@@
     const item = link.closest('li')
-    if (item) item.style.display = visible > 0 ? '' : 'none'
+    if (item) {
+      const show = visible > 0
+      item.style.display = show ? '' : 'none'
+      if (show) visibleItems++
+    }
@@
   })
+
+  const nav = document.querySelector('.category-nav')
+  if (nav) nav.style.display = visibleItems > 0 ? '' : 'none'
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/src/components/card-grid.js` around lines 504 - 509, Beim
Aktualisieren der einzelnen <li> und der .category-nav-counts berechne
anschließend, ob über alle Links hinweg irgendein visible > 0 existiert; wenn
nicht, verstecke den gesamten Quick-Nav-Container (z. B. das umgebende
Sticky-Element) statt nur die einzelnen Listeneinträge. Konkret: nach der
Schleife summiere die visible-Werte (oder prüfe, ob mindestens ein
countEl.textContent !== '0'), finde das umgebende Wrapper-Element (z. B. via
link.closest('<wrapper-selector>') oder document.querySelector('.quick-nav' /
'.category-quick-nav') ) und setze dessen style.display = 'none' wenn die Summe
0 ist, sonst zeige es wieder (''), damit der leere sticky Container vollständig
verschwindet.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@website/src/components/card-grid.js`:
- Around line 124-126: Das aria-label der Quick-Nav wird nur beim initialen
Rendern gesetzt und nicht bei späteren Sprachwechseln aktualisiert; aktualisiere
die Stelle, die das <nav class="category-nav"> rendert (wo
aria-label="${escapeHtml(i18n.t('nav.categoryJump'))}" gesetzt wird) so, dass
zusätzlich ein data-i18n-aria="nav.categoryJump" Attribut hinzugefügt wird,
damit applyTranslations() die Übersetzung später aktualisieren kann; stelle
sicher, dass der Schlüssel "nav.categoryJump" verwendet wird (gleich wie beim
aktuellen i18n.t-Aufruf) und dass escapeHtml weiterhin für die initiale
aria-label-Werteinsetzung verwendet wird.

In `@website/src/styles/main.css`:
- Around line 100-138: The CSS rules violate the declaration-empty-line-before
lint rule; update each selector block (.category-nav, .category-nav-list,
.category-nav-link, .category-nav-count) to insert a blank line before the
relevant declarations (e.g., before the `@apply` lines, before
background/border/color declarations) so there is an empty line separating
declaration groups as required by declaration-empty-line-before; keep the
existing properties and order, only add the missing blank lines to satisfy
Stylelint.

---

Nitpick comments:
In `@website/src/components/card-grid.js`:
- Around line 504-509: Beim Aktualisieren der einzelnen <li> und der
.category-nav-counts berechne anschließend, ob über alle Links hinweg irgendein
visible > 0 existiert; wenn nicht, verstecke den gesamten Quick-Nav-Container
(z. B. das umgebende Sticky-Element) statt nur die einzelnen Listeneinträge.
Konkret: nach der Schleife summiere die visible-Werte (oder prüfe, ob mindestens
ein countEl.textContent !== '0'), finde das umgebende Wrapper-Element (z. B. via
link.closest('<wrapper-selector>') oder document.querySelector('.quick-nav' /
'.category-quick-nav') ) und setze dessen style.display = 'none' wenn die Summe
0 ist, sonst zeige es wieder (''), damit der leere sticky Container vollständig
verschwindet.

In `@website/src/styles/main.css`:
- Around line 140-142: Die globale Regel "html { scroll-behavior: smooth; }"
überschreibt Nutzerpräferenzen für reduzierte Bewegung; fügen Sie eine
CSS-Media-Query für "prefers-reduced-motion: reduce" hinzu und setzen darin die
Regel für den "html" Selektor auf "scroll-behavior: auto" (ggf. mit !important)
so dass Nutzer mit reduziertem Motion-Setting wieder die
Standard-Scrollverhalten erhalten.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e331b89f-2d3b-4ad6-8f4b-bfb0bb8931b7

📥 Commits

Reviewing files that changed from the base of the PR and between e0fb2ee and 5ac798c.

📒 Files selected for processing (5)
  • website/src/components/card-grid.js
  • website/src/components/card-grid.test.js
  • website/src/styles/main.css
  • website/src/translations/de.json
  • website/src/translations/en.json

Comment thread website/src/components/card-grid.js Outdated
Comment on lines +100 to +138
.category-nav {
position: sticky;
top: 0;
z-index: 30;
/* Hidden on phones (search + role filter cover navigation there, and the
sticky strip would eat scarce vertical space); shown from sm upward. */
@apply hidden sm:block mb-8 py-2;
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
}

.category-nav-list {
@apply flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto;
scrollbar-width: thin;
}

.category-nav-link {
@apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0;
@apply transition-colors duration-150;
border: 1px solid var(--color-border);
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
}

.category-nav-link:hover,
.category-nav-link:focus-visible {
color: var(--color-text);
border-color: var(--color-primary);
}

.category-nav-icon {
@apply text-base leading-none;
}

.category-nav-count {
@apply text-xs font-semibold px-1.5 rounded-full;
background: var(--color-bg);
color: var(--color-text-secondary);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stylelint-Fehler in der neuen Quick-Nav-CSS beheben

In diesem Block fehlen die geforderten Leerzeilen vor einzelnen Deklarationen (u. a. Line 107, Line 113, Line 119, Line 136), was aktuell gegen declaration-empty-line-before verstößt und CI/Lint brechen kann.

Vorgeschlagener Fix
 .category-nav {
   position: sticky;
   top: 0;
   z-index: 30;
   /* Hidden on phones (search + role filter cover navigation there, and the
      sticky strip would eat scarce vertical space); shown from sm upward. */
   `@apply` hidden sm:block mb-8 py-2;
+
   background: var(--color-bg);
   border-bottom: 1px solid var(--color-border);
 }
 
 .category-nav-list {
   `@apply` flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto;
+
   scrollbar-width: thin;
 }
 
 .category-nav-link {
   `@apply` inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0;
   `@apply` transition-colors duration-150;
+
   border: 1px solid var(--color-border);
   background: var(--color-bg-secondary);
   color: var(--color-text-secondary);
 }
 
 .category-nav-count {
   `@apply` text-xs font-semibold px-1.5 rounded-full;
+
   background: var(--color-bg);
   color: var(--color-text-secondary);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.category-nav {
position: sticky;
top: 0;
z-index: 30;
/* Hidden on phones (search + role filter cover navigation there, and the
sticky strip would eat scarce vertical space); shown from sm upward. */
@apply hidden sm:block mb-8 py-2;
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
}
.category-nav-list {
@apply flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto;
scrollbar-width: thin;
}
.category-nav-link {
@apply inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0;
@apply transition-colors duration-150;
border: 1px solid var(--color-border);
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
}
.category-nav-link:hover,
.category-nav-link:focus-visible {
color: var(--color-text);
border-color: var(--color-primary);
}
.category-nav-icon {
@apply text-base leading-none;
}
.category-nav-count {
@apply text-xs font-semibold px-1.5 rounded-full;
background: var(--color-bg);
color: var(--color-text-secondary);
}
.category-nav {
position: sticky;
top: 0;
z-index: 30;
/* Hidden on phones (search + role filter cover navigation there, and the
sticky strip would eat scarce vertical space); shown from sm upward. */
`@apply` hidden sm:block mb-8 py-2;
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
}
.category-nav-list {
`@apply` flex flex-nowrap gap-2 list-none p-0 m-0 overflow-x-auto;
scrollbar-width: thin;
}
.category-nav-link {
`@apply` inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full no-underline whitespace-nowrap flex-shrink-0;
`@apply` transition-colors duration-150;
border: 1px solid var(--color-border);
background: var(--color-bg-secondary);
color: var(--color-text-secondary);
}
.category-nav-link:hover,
.category-nav-link:focus-visible {
color: var(--color-text);
border-color: var(--color-primary);
}
.category-nav-icon {
`@apply` text-base leading-none;
}
.category-nav-count {
`@apply` text-xs font-semibold px-1.5 rounded-full;
background: var(--color-bg);
color: var(--color-text-secondary);
}
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 107-107: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 113-113: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 119-119: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 136-136: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@website/src/styles/main.css` around lines 100 - 138, The CSS rules violate
the declaration-empty-line-before lint rule; update each selector block
(.category-nav, .category-nav-list, .category-nav-link, .category-nav-count) to
insert a blank line before the relevant declarations (e.g., before the `@apply`
lines, before background/border/color declarations) so there is an empty line
separating declaration groups as required by declaration-empty-line-before; keep
the existing properties and order, only add the missing blank lines to satisfy
Stylelint.

The category quick-nav's <nav aria-label> was set only at render time, so
a later language switch left it stale (applyTranslations only updates
elements carrying data-i18n-aria). Add data-i18n-aria="nav.categoryJump"
so the label follows the active language, matching the chips. Addresses
CodeRabbit review on LLM-Coding#549.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rdmueller rdmueller merged commit 05d0c75 into LLM-Coding:main Jun 1, 2026
6 of 7 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.

2 participants