fix(sidebar): omit changelog badge for anon visitors + clarify CLAUDE.md auth gate (t-paliad-035)

The marketing landing (`/`) renders the same Sidebar as protected pages, so
`initChangelogBadge()` was firing `GET /api/changelog/unseen-count` on every
anon visit and getting 401. Cosmetic noise + wasted round-trip.

Add an `authenticated` prop to Sidebar (defaults to true, no behavior change
on protected pages) and pass `false` from `renderIndex()`. The badge `<a>`
is omitted server-side; the existing `if (!badge) return` guard in
sidebar.ts naturally skips the fetch when the element is absent — no
client change needed.

Also append a clarifying note under the env-var table in .claude/CLAUDE.md:
"work without DB" doesn't mean "ungated for anon". The knowledge-platform
routes (Kostenrechner, Glossar, etc.) are still behind the auth gate; only
`/`, `/login`, `/logout`, and `/assets/*` are public. Misread by the smoke
tester briefer; spelled out now to prevent recurrence.
This commit is contained in:
m
2026-04-25 23:09:36 +02:00
parent 0ad2e86945
commit 83d5973dd6
3 changed files with 17 additions and 7 deletions

View File

@@ -25,6 +25,12 @@ const ICON_USERS = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" s
interface SidebarProps {
currentPath: string;
// authenticated defaults to true; pass false on anon-facing pages (the
// marketing landing) so we don't render auth-only affordances that would
// fire 401s. Currently gates only the changelog badge link — the badge
// client (sidebar.ts initChangelogBadge) early-returns when the link is
// absent, so there is no client change needed.
authenticated?: boolean;
}
function navItem(href: string, icon: string, i18nKey: string, label: string, currentPath: string): string {
@@ -60,7 +66,7 @@ function group(i18nKey: string, label: string, children: string): string {
);
}
export function Sidebar({ currentPath }: SidebarProps): string {
export function Sidebar({ currentPath, authenticated = true }: SidebarProps): string {
return (
<Fragment>
<aside className="sidebar">
@@ -129,11 +135,13 @@ export function Sidebar({ currentPath }: SidebarProps): string {
<div className="sidebar-spacer" />
<div className="sidebar-bottom">
<a href="/changelog" className={`sidebar-item sidebar-changelog${currentPath === "/changelog" ? " active" : ""}`} id="sidebar-changelog-link">
<span className="sidebar-icon" dangerouslySetInnerHTML={{ __html: ICON_SPARKLE }} />
<span className="sidebar-label" data-i18n="nav.neuigkeiten">Neuigkeiten</span>
<span className="sidebar-badge" id="sidebar-changelog-badge" style="display:none" aria-hidden="true" />
</a>
{authenticated ? (
<a href="/changelog" className={`sidebar-item sidebar-changelog${currentPath === "/changelog" ? " active" : ""}`} id="sidebar-changelog-link">
<span className="sidebar-icon" dangerouslySetInnerHTML={{ __html: ICON_SPARKLE }} />
<span className="sidebar-label" data-i18n="nav.neuigkeiten">Neuigkeiten</span>
<span className="sidebar-badge" id="sidebar-changelog-badge" style="display:none" aria-hidden="true" />
</a>
) : ""}
<button type="button" className="sidebar-item sidebar-invite-btn" id="sidebar-invite-btn">
<span className="sidebar-icon" dangerouslySetInnerHTML={{ __html: ICON_MAIL }} />
<span className="sidebar-label" data-i18n="invite.button">Kolleg:in einladen</span>

View File

@@ -23,7 +23,7 @@ export function renderIndex(): string {
<link rel="stylesheet" href="/assets/global.css" />
</head>
<body className="has-sidebar">
<Sidebar currentPath="/" />
<Sidebar currentPath="/" authenticated={false} />
<main>
<section className="hero">