← all branches

content/auto-draft

needs attentionviewing older commit
8b79d33 · incrementalPR #304reviewed 2026-07-14 18:10 UTC5H · 7M · 11L · 8I
The branch
Purpose
Improve the blog post reading experience with reusable UI components (PostImage lightbox, sticky TOC sidebar) and add a Tarifas-module screenshot to the GDMTH article.
Goal
Ship PostImage + TableOfContents components to enhance GDMTH blog post visual presentation and navigation UX
Sub-goals
  • PostImage: click-to-zoom lightbox with keyboard and caption CTA support
  • TableOfContents: sticky scroll-spy sidebar hidden on mobile, visible on xl+
  • tarifa-gdmth.mdx: add Tarifas-module screenshot via PostImage
  • page.tsx: article+aside flex layout; Button variant cancel→secondary fix
The changes (whole branch)
What
Added two new client components (PostImage lightbox, TableOfContents scroll-spy) and restructured the blog post page layout to support a sticky sidebar TOC on xl+ screens while keeping an inline TOC for mobile. The GDMTH article was updated with a product screenshot.
Why
Richer blog content (screenshots, interactive TOC) improves engagement and supports the content-pipeline's autonomous draft flow.
Areas
apps/web/src/components/blog+1380apps/web/src/app/(marketing)/blog+12920apps/web/content/blog+2141apps/web/src/styles+82
Blast
~500 lines across apps/web only (blog content + UI components); no API, domain, or infra changes.
missing-focus-trap multi-line-docstrings wcag-a11y
vercel· batu-codebase-web deployed to previewci· No required CI checks for this branchcoderabbit· No .coderabbit.yaml in repo

Findings · 12

correctness5

high

No focus trap in lightbox dialog

apps/web/src/components/blog/PostImage.tsx:97

role=dialog aria-modal has no Tab trap. Keyboard users escape the modal. WCAG 2.1 SC 2.1.2 violation.

high

Dialog never receives initial focus on open

apps/web/src/components/blog/PostImage.tsx:97

When open becomes true, focus stays on the thumbnail button. Screen readers don't perceive the modal opened. Fix: focus the close button in a useEffect([open]).

high

Scroll-lock cleanup clears competing PostImage's lock

apps/web/src/components/blog/PostImage.tsx:52

Cleanup always resets body.style.overflow='' — clears another open lightbox's scroll lock if two PostImages coexist. Use classList.toggle instead of inline style.

medium

IntersectionObserver rootMargin -70% misses last section on short pages

apps/web/src/app/(marketing)/blog/_components/TableOfContents.tsx:25

Last section heading may never enter the active zone if its section is shorter than 70% of viewport. Active state stays stuck on second-to-last item.

medium

Initial active state stale after SPA route change

apps/web/src/app/(marketing)/blog/_components/TableOfContents.tsx:14

useState initializes to items[0].id once. On soft navigation reusing the layout, active briefly shows the first item of the old set.

security1

medium

No CSP headers on marketing site (pre-existing)

Not introduced here but the raw img in the lightbox needs an img-src carve-out when CSP is eventually added.

conventions3

high

Multi-paragraph JSDoc violates one-line-max rule

apps/web/src/components/blog/PostImage.tsx:9

7-line JSDoc block. CLAUDE.md: 'Never write multi-paragraph docstrings or multi-line comment blocks — one short line max.'

high

Multi-paragraph JSDoc violates one-line-max rule

apps/web/src/app/(marketing)/blog/_components/TableOfContents.tsx:7

5-line JSDoc block. Same rule violation. Remove or condense to one line.

medium

Multi-line comment on getToc function

apps/web/src/app/(marketing)/blog/[slug]/page.tsx:58

Three-line comment block. The only non-obvious part fits in one line.

improvement3

medium

Empty aside occupies 240px layout at xl+ when toc < 4 items

apps/web/src/app/(marketing)/blog/[slug]/page.tsx:317

TableOfContents returns null but the aside wrapper with w-60 is always rendered. At xl+ this takes 240px of layout space with nothing inside.

medium

open/zoomed two-boolean state encodes invalid combination

apps/web/src/components/blog/PostImage.tsx:37

open=false+zoomed=true is reachable. A single type LightboxState = 'closed'|'open'|'zoomed' eliminates the invalid state.

medium

TOC threshold (>= 4) split across two files

apps/web/src/app/(marketing)/blog/[slug]/page.tsx:244

Changing the threshold requires edits in page.tsx and TableOfContents.tsx. Extract to a shared constant.

History · 14 commits

  1. c27ea57needs attentionincremental3H · 5M · 4L2026-08-04 17:46
  2. ff69645needs attentionincremental0H · 6M · 8L2026-07-28 18:21
  3. 0257d74safeincremental0H · 0M · 0L2026-07-20 23:00
  4. 0795a9dsafeincremental0H · 0M · 2L2026-07-20 22:42
  5. 5c57414needs attentionincremental2H · 7M · 10L2026-07-20 22:29
  6. 5a57a8fneeds attentionincremental0H · 5M · 8L2026-07-20 17:14
  7. cc9056eneeds attentionfull1H · 3M · 9L2026-07-20 16:40
  8. 8b79d33needs attentionincremental5H · 7M · 11L2026-07-14 18:10current
  9. f0c337cneeds attentionincremental1H · 1M · 5L2026-07-14 17:15
  10. 5c9de97needs attentionincremental0H · 5M · 7L2026-07-14 17:02
  11. 92cce08needs attentionincremental1H · 4M · 3L2026-07-14 02:00
  12. b7dd610needs attentionincremental0H · 6M · 7L2026-07-14 01:43
  13. 8c43554needs attentionfull0H · 2M · 1L2026-07-14 01:15
  14. 2149391blockedfull8H · 6M · 7L2026-07-10 17:30