Skip to content

Phase C — Products subtree (empty state + Thalura draft fixture)#12

Merged
blackbrowed-labs merged 7 commits into
mainfrom
dev
Apr 29, 2026
Merged

Phase C — Products subtree (empty state + Thalura draft fixture)#12
blackbrowed-labs merged 7 commits into
mainfrom
dev

Conversation

@larsweiser
Copy link
Copy Markdown
Collaborator

@larsweiser larsweiser commented Apr 29, 2026

Summary

Phase C ships the Products subtree on blackbrowedlabs.com:

  • Public surface: /produkte + /en/products render the BASELINE §5 empty-state copy until a non-draft product launches. Nav and footer gain Products as the first entry on every page.
  • Detail-template stub: [slug].astro × 2 with eyebrow + H1 + tagline + description + opt-in CTA (primary on externalUrl; ghost-fallback on repo only when showGithubLink: true). getStaticPaths returns [] on prod; renders Thalura on dev.
  • Schema additions: releaseDate (scheduled publishing), optional externalUrl/repo with .refine() requiring at least one, showGithubLink opt-in for GitHub CTA.
  • First product: Thalura für Claude Cowork / Thalura for Claude Cowork as draft: true test fixture — visible on dev, hidden on prod. CTA suppressed because the plugin repo is currently private.
  • Cleanup fold-ins from G C.1 + G C.2.a reviews: AAA dark-mode CTA contrast fix, design-system token alignment, doc bold-marker restoration, dark-mode selector idiom unification, breadcrumb aria-label i18n retrofit across all 12 breadcrumb-bearing pages (closing a pre-existing locale-mismatch bug on /ueber + /kontakt).
  • Doc revisions: TECH_STACK §1.2 + §13.5 + §5.2; DESIGN_BRIEF §7.

Six commits across three gates (G C.1, G C.2.a, G C.2.b) with three in-gate fix-ups landed within their respective gate windows.

Test plan

  • /produkte and /en/products render the empty-state copy verbatim
  • Nav and footer order on every page: Products first
  • All 12 breadcrumb-bearing pages serve locale-correct aria-label (DE: "Brotkrümelnavigation", EN: "Breadcrumb")
  • /produkte/thalura-plugin and /en/products/thalura-plugin return 404 on production (draft filter holds)
  • 4 live legal pages (impressum, datenschutz, en/legal, en/privacy) still serve correctly post-retrofit
  • CWA beacon still fires on production
  • Email delivery to lars@blackbrowedlabs.com still works (DNS untouched)

🤖 Generated with Claude Code

- src/pages/produkte/index.astro + en/products/index.astro: empty state from
  BASELINE_COPY.md §5 verbatim, single-column centered, atmospheric arc
  bottom-right at low opacity (8% light / 14% dark / 6% mobile). Populated
  branch wired but unexercised in v1 (no products in the collection).
- src/lib/products.ts: getVisibleProducts(lang) helper, interim form —
  filters by isProduction() + draft. The releaseDate branch lands in
  G C.2.a alongside the schema field.
- SiteHeader / SiteFooter: Products entry first per design bundle order;
  re-orders existing About/Contact entries.
- src/i18n/{de,en}.ts: new productsIndex namespace (h1, breadcrumb,
  populated arrow label, arc aria-label).
- Doc revisions softening "no product names until launch" to "no names
  on production; dev may carry drafted/scheduled named entries":
  TECH_STACK §1.2, §13.5; DESIGN_BRIEF §7.
- src/content.config.ts: products schema additions — releaseDate
  (z.coerce.date().optional()), externalUrl/repo both optional with
  .refine() requiring at least one. Schema additions are forward-defensive;
  no product entries exercise them yet (G C.2.b lands the first).
- src/lib/products.ts: helper extended to filter on releaseDate > now in
  production, complementing the draft gate.
- src/pages/produkte/[slug].astro + en mirror: minimal-stub detail
  template (eyebrow + H1 + tagline + description + CTA). Version chip and
  release-history stack are deliberately deferred to Phase D when real
  release data exists.
- docs/TECH_STACK.md §5.2: documents visibility gates (draft + releaseDate)
  and the schema refine semantics.
…m tokens

Addresses two defects surfaced by G C.2.a code-quality review.

BLOCKER fix — dark-mode primary-CTA contrast:
- .cta--primary's `color: white` hardcoded in both detail templates fails
  AA (and AAA) in dark mode: white on #D4A45C ≈ 1.95:1.
  CLAUDE_DESIGN_BRIEF §4.5 mandates dark text on dark-mode color chips.
  Replace with var(--color-cta-text) which is already wired in tokens.css
  to swap #FFFFFF → #1C1C1E by mode (matches the ContactForm precedent).

MAJOR fix — undefined CTA hover tokens:
- var(--color-primary-hover) → var(--color-bbl-primary-hover) (project's
  existing semantic alias at tokens.css:115).
- var(--color-bg-card) → var(--color-surface-card) (project's semantic
  alias for bg-card per CLAUDE_DESIGN_BRIEF §4.4 + §4.7, at tokens.css:54).
- Originals were undefined; CSS silently dropped the hover declarations.

Cheap MINOR fixes folded in:
- .product-detail__container declared max-width twice; drop the dead
  var(--max-width-site) line (the 70ch override was already winning).
- rel="noopener" extended to the external-website CTA branch (was only
  on the GitHub-fallback). Security baseline for third-party links.

Verification:
- npm run check: 0 errors.
- npm run build: succeeds; 13 pages built.
- Empty-state regression: /produkte + /en/products still render the
  BASELINE §5 copy verbatim.
Primary scope:
- src/content/products/thalura-plugin.{de,en}.md: first product entry
  (Thalura für Claude Cowork / Thalura for Claude Cowork). draft: true →
  visible on dev only, hidden on prod via env-aware filter. CTA falls
  back to GitHub repo URL (thalura.app/.de domains registered but not
  yet landing-page-live).

Cleanup fold-ins from G C.1 review:
- Tap-target fix on .products-index__more: min-height 44px + flex
  alignment so the populated-grid arrow link meets AAA when the branch
  goes live with this gate.
- Doc bold-marker restoration in TECH_STACK §1.2 + §13.5 and
  DESIGN_BRIEF §7 (G C.1's verbatim plan replacements dropped lead-
  clause bold; sibling list items use bold to anchor each rule).
- Dark-mode selector idiom unified: :global([data-theme="dark"]) →
  :root[data-theme="dark"] in the Products index pages, matching the
  ThemeToggle.astro precedent.
- Breadcrumb aria-label moved to i18n: src/i18n/{de,en}.ts gain
  breadcrumb.ariaLabel ('Brotkrümelnavigation' / 'Breadcrumb');
  retrofit applied to all 8 breadcrumb-bearing pages (impressum,
  datenschutz, en/legal, en/privacy, produkte/index, en/products/index,
  produkte/[slug], en/products/[slug]).

Verification:
- Default build (PUBLIC_ENVIRONMENT unset): Thalura visible, locale-
  correct breadcrumb labels on all 8 pages, tap-target compliant.
- Production build (PUBLIC_ENVIRONMENT=production): Thalura filtered;
  retrofit holds on the 4 live legal pages without regression.
…ed pages

Closes the breadcrumb i18n retrofit gap surfaced by both G C.2.b reviewers:
my spec said "all 8 breadcrumb-bearing pages" but the site actually has 12.
Four pages were left out and continued to hardcode aria-label="Breadcrumb",
including two DE pages (ueber, kontakt) that consequently served the English
label to German screen-reader users — the locale-mismatch bug fold-in #4
was meant to eliminate.

Pages retrofitted:
- src/pages/ueber.astro              (DE → Brotkrümelnavigation)
- src/pages/kontakt.astro            (DE → Brotkrümelnavigation)
- src/pages/en/about.astro           (EN → Breadcrumb, now i18n-driven)
- src/pages/en/contact.astro         (EN → Breadcrumb, now i18n-driven)

Each page gains:
- Import: getUiStrings from the appropriate relative path
- Const: t = getUiStrings('de'|'en')
- aria-label="Breadcrumb" → aria-label={t.breadcrumb.ariaLabel}

No other changes; no content/whitespace/CSS drift on these pages.

Verification:
- npm run check: 0 errors.
- npm run build: 15 pages.
- All 12 breadcrumb-bearing pages now serve their locale-correct
  aria-label: 6 DE pages ("Brotkrümelnavigation"), 6 EN pages
  ("Breadcrumb"). Zero pages still hardcode "Breadcrumb" in DE markup.
…age switcher

Two findings from the G C.2.b dev walk, both addressed in one fix-up commit
within the gate's reserved fix-up budget.

Finding 1 — GitHub fallback CTA points at private repo:
The G C.2.a auto-fallback rendered a "View on GitHub" CTA whenever
externalUrl was absent. That broke for products with private repos
(thalura-plugin is private currently): the CTA pointed at a 404 / sign-in
page for unauthenticated visitors. Inverted the default to opt-in:

- Schema gains `showGithubLink: z.boolean().optional()`.
- Detail template renders the GitHub CTA only when
  `showGithubLink === true` AND `repo` is set.
- Thalura entries don't set the flag → no CTA on detail pages, exactly
  what we want while the repo is private.

Conservative default: forgetting the flag means "no CTA renders," not
"broken CTA points at 404." Phase D's release loader is expected to
supersede this manual flag with GitHub-API auto-detection of repo
visibility — the flag then becomes either redundant or a force-show /
force-hide override.

Finding 2 — Language switcher dead on product detail pages:
src/lib/i18n.ts carried a static counterpart map covering only named
pages (homepage, about, products index, etc.). Dynamic detail pages
(/produkte/<slug>) had no map entry, so getCounterpartUrl() returned
null and the LanguageSwitcher rendered aria-disabled.

Since detail-page slugs are deliberately the same across DE and EN
(per TECH_STACK §5.2), extended getCounterpartUrl() with a deterministic
slug-pattern fallback:

  /produkte/<slug>      ↔  /en/products/<slug>

getHreflangAlternates() refactored to call getCounterpartUrl() so both
the switcher AND the hreflang link tags benefit from the dynamic logic.

Doc: TECH_STACK §5.2 documents `showGithubLink` semantics + the Phase D
auto-detect supersedes-this forward note.

Verification:
- npm run check: 0 errors.
- npm run build: 15 pages.
- Thalura DE + EN detail pages render WITHOUT a CTA (0 grep matches for
  "Auf GitHub ansehen" / "View on GitHub" — finding 1 closed).
- Thalura DE detail page emits hreflang="en" href=".../thalura-plugin"
  and EN detail page emits hreflang="de" href=".../thalura-plugin" —
  finding 2 closed.
@blackbrowed-labs blackbrowed-labs merged commit 7e0949c into main Apr 29, 2026
2 checks passed
blackbrowed-labs added a commit that referenced this pull request May 12, 2026
…+ J.1 conventions doc)

Lifts ~862 LOC of byte-identical scoped CSS out of the four products page
templates (produkte/index, produkte/[slug], en/products/index,
en/products/[slug]) into a single src/styles/products.css module, imported
once via global.css. Closes Pass 2 backlog row #12.

Class names stay BEM-namespaced (products-index__*, product-detail__*,
release__*, breadcrumb__*, version-chip*, cta*) — no markup churn. All
four scoped <style> blocks are deleted in their entirety; no page-private
CSS survives because the DE/EN siblings were byte-identical except for
documentation comments.

Inside the .release__body block, the previous :global() pseudo-wrappers
(needed for Astro-scoped <style> to reach raw markdown elements rendered
via set:html) are removed — the new module is non-scoped so the selectors
target the rendered <h1>/<p>/<ul>/etc. directly.

Co-introduces docs/CSS_CONVENTIONS.md (Phase J.1, folded into this commit
per the J plan's J.1 standalone-vs-fold decision). The doc records the
module-location, class-naming, extraction-scope, specificity, and import-
order disciplines that J.2 enforces and J.3 will follow.

Verified locally: npm run check 0 errors / 0 warnings, npm run build clean,
dist/_astro CSS bundle drops 56,575 → 43,203 bytes (−13.1 KB raw, four
per-route _slug_/index@_@astro.*.css chunks merged into BaseLayout's
single bundle). Every products class still emits in built HTML (grep);
no astro- scoped hashes leak. Real-browser staging walk deferred to
Wave 4 integration PR per controller instruction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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