Skip to content

feat(bulma-ui): add Reveal component for scroll-triggered animations - #251

Closed
bestaxbot wants to merge 2 commits into
mainfrom
claude/issue-197-20260707-1246
Closed

bestaxbot wants to merge 2 commits into
mainfrom
claude/issue-197-20260707-1246

Conversation

@bestaxbot

@bestaxbot bestaxbot commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Implements the Reveal component proposed in #197: a lightweight wrapper backed by IntersectionObserver that animates content into view as it scrolls into the viewport — fade, fade-up, fade-down, slide-left, slide-right, zoom, flip — plus a cascade mode that staggers direct children with an incrementing delay.

Accessibility/progressive enhancement is built in, not opt-in:

  • Skips the animation and renders the final, visible state when prefers-reduced-motion: reduce is set.
  • Renders the final, visible state during SSR and on the first client render, so content is never hidden if JS never runs.
  • Falls back to visible immediately if IntersectionObserver isn't available.
  • Handles plain (non-forwardRef) components passed via as (e.g. Section, Card) by falling back to an internal wrapper div for scroll observation, so the ref always attaches — with a regression test covering it.

This is the fourth attempt at this issue: PR #243 and PR #247 each fully implemented and passed all gates but were closed without merging (no reason recorded); a third attempt was blocked purely on tooling permissions and never opened a PR. This PR carries forward the same finalized implementation, rebuilt on a fresh branch off current main and re-verified from scratch.

Changes

  • bulma-ui/src/components/Reveal.tsx — the component
  • bulma-ui/src/components/Reveal.stories.tsx — Storybook stories (Default, Animations, AsSection, Cascade, ScrollToReveal)
  • bulma-ui/src/components/__tests__/Reveal.test.tsx — 22 tests, 100% coverage on Reveal.tsx
  • bulma-ui/src/scss/components/_reveal.scss (+ registered in _index.scss) — CSS-variable-driven animation styles, including a prefers-reduced-motion fallback
  • bulma-ui/src/index.ts — public export
  • docs/docs/api/components/reveal.md — API docs page
  • skills/bestax-custom-component/references/component-catalog.md — regenerated (pnpm gen:catalog)

Test plan

  • pnpm --filter @allxsmith/bestax-bulma run typecheck — clean
  • pnpm --filter @allxsmith/bestax-bulma run lint — 0 errors (only pre-existing unrelated warnings)
  • pnpm --filter @allxsmith/bestax-bulma run test:coverage — 3353 tests pass, Reveal.tsx 100% coverage, overall thresholds held (99.37% stmts / 99.05% branches)
  • pnpm run format:check — clean
  • pnpm run gen:catalog:check — catalog matches

Fixes #197

Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new scroll-triggered Reveal component with configurable animation, timing, and thresholds.
    • Included supported animation effects, cascading stagger behavior, and as rendering support.
    • Added Storybook stories showcasing default, animations, section usage, cascading, and scroll-trigger examples.
  • Documentation
    • Added API documentation for Reveal, including reduced-motion, SSR, and fallback behavior.
    • Updated the component catalog to include Reveal.
  • Bug Fixes
    • Improved reduced-motion handling and SSR/IntersectionObserver fallbacks to keep content visible.
  • Tests
    • Added a comprehensive test suite covering intersection behavior, once/replay, cascade timing, and reduced-motion updates.

Adds a lightweight IntersectionObserver-backed wrapper that animates
content into view as it scrolls into the viewport (fade, fade-up,
fade-down, slide-left, slide-right, zoom, flip), with a cascade mode
that staggers direct children.

Accessibility and progressive enhancement are built in: it skips the
animation under prefers-reduced-motion, renders the final visible
state during SSR and on first client render, and falls back to
visible immediately when IntersectionObserver is unavailable. A
custom (non-forwardRef) component passed via `as` falls back to an
internal wrapper div so scroll observation still attaches a ref.

Fixes #197

Co-authored-by: Alex Smith <allxsmith@users.noreply.github.com>
@bestaxbot bestaxbot added the ai-loop AI-authored PR in the autonomous review/fix loop label Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 06bdeeb3-22f1-46cc-b8e5-9e7196f37a83

📥 Commits

Reviewing files that changed from the base of the PR and between 8e516c8 and 7bca0e9.

📒 Files selected for processing (2)
  • docs/docs/api/components/reveal.md
  • skills/bestax-custom-component/references/component-catalog.md
✅ Files skipped from review due to trivial changes (1)
  • skills/bestax-custom-component/references/component-catalog.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/docs/api/components/reveal.md

Walkthrough

Adds a new Reveal component for scroll-triggered viewport animations, plus SCSS styling, tests, Storybook stories, docs, and top-level package wiring. It supports reduced motion, SSR-safe rendering, configurable animation options, and cascade child staggering.

Changes

Reveal Component

Layer / File(s) Summary
Reveal types and props contract
bulma-ui/src/components/Reveal.tsx
Defines RevealAnimation and RevealProps for animation, timing, threshold, cascade, and as behavior.
Reduced-motion hook and intersection logic
bulma-ui/src/components/Reveal.tsx
Implements reduced-motion detection, threshold clamping, and IntersectionObserver reveal state updates.
Class/style computation and cascade rendering
bulma-ui/src/components/Reveal.tsx, bulma-ui/src/index.ts
Computes reveal classes and inline timing, staggers cascade children, handles as rendering, and re-exports Reveal at the package root.
SCSS styling and build wiring
bulma-ui/src/scss/components/_reveal.scss, bulma-ui/src/scss/components/_index.scss
Adds reveal effect styles, reduced-motion overrides, and includes the partial in the component stylesheet index.
Test suite
bulma-ui/src/components/__tests__/Reveal.test.tsx
Covers rendering, as handling, threshold behavior, observer transitions, reduced motion, cascade, and SSR output.
Storybook stories
bulma-ui/src/components/Reveal.stories.tsx
Adds Default, Animations, AsSection, Cascade, and ScrollToReveal stories with Storybook controls.
API docs and catalog entry
docs/docs/api/components/reveal.md, skills/bestax-custom-component/references/component-catalog.md
Documents props, usage examples, accessibility notes, and adds the component to the catalog.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant Reveal
  participant IntersectionObserver
  participant DOM

  Browser->>Reveal: mount component
  Reveal->>Reveal: detect reduced motion
  alt motion reduced
    Reveal->>DOM: render visible state
  else motion allowed
    Reveal->>IntersectionObserver: observe with threshold
    IntersectionObserver-->>Reveal: enter viewport
    Reveal->>DOM: apply revealed classes
  end
Loading

Suggested labels: needs-human-review

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a Reveal component for scroll-triggered animations.
Description check ✅ Passed It includes a clear summary, affected package, linked issue, changes, and test plan, though it doesn't use the exact template format.
Linked Issues check ✅ Passed The PR implements the requested Reveal component, variants, cascade behavior, reduced-motion handling, SSR safety, and as-prop support from #197.
Out of Scope Changes check ✅ Passed The changes stay focused on the new Reveal component and its supporting docs, tests, stories, styles, export, and catalog updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-197-20260707-1246

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://da6bb4af.bestax.pages.dev

Comment thread docs/docs/api/components/reveal.md Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deep review — 1 finding

# Severity Area Finding Location
1 🟡 Minor API Shipped skills-catalog entry truncated mid-sentence ("...viewport, backed"); the Overview's first line wraps mid-sentence so the generator emits a dangling half-sentence docs/docs/api/components/reveal.md:10

Overall: This is a clean, well-engineered addition. The Reveal component gets the hard parts right — SSR renders the final visible state so first-paint markup matches the server (no hydration mismatch), prefers-reduced-motion and missing-IntersectionObserver both fall back to the revealed state, threshold is clamped/guarded against the RangeError that IO throws for NaN/out-of-range, and the non-forwardRef as={Component} case is handled by wrapping in an observed div and documented in three places. Tests are thorough (26 passing, 100% stmt/branch/func/line on the new file), and the story, API docs page, and regenerated catalog are all present. The only real defect is the truncated catalog description, which ships to LLM consumers via create-bestax. Worth a human eye (non-blocking): the as={Component} case puts className/helper classes on the wrapper div rather than the component — necessary and documented, but a behavioral divergence from other as-supporting components; and above-the-fold content will briefly flash visible→hidden→animate-in on hydration, an inherent tradeoff of the SSR-safe approach.

🏄 Totally smooth ride, dude — this Reveal catches the scroll wave clean, bails gracefully when the user wants no motion, and doesn't wipe out on SSR. Just one gnarly little sentence that got clipped before it hit the beach; patch that and it's good to paddle out.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (3)
bulma-ui/src/scss/components/_reveal.scss (1)

31-43: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

will-change persists indefinitely after reveal.

will-change: opacity, transform is applied to the base class (Line 42) and never removed once .is-revealed is added (Lines 70-79). Per MDN guidance, will-change "implies that the targeted elements are always a few moments away from changing" and browsers "keep the optimizations for much longer time than it would have otherwise" when set directly in a stylesheet rather than toggled via script. Given this component targets landing pages with many staggered/cascaded elements, leaving will-change on indefinitely can force the browser to maintain composited layers for all revealed elements, increasing memory usage.

♻️ Proposed fix: drop the will-change hint once revealed
 .#{iv.$class-prefix}reveal-fade.#{iv.$class-prefix}is-revealed,
 .#{iv.$class-prefix}reveal-fade-up.#{iv.$class-prefix}is-revealed,
 .#{iv.$class-prefix}reveal-fade-down.#{iv.$class-prefix}is-revealed,
 .#{iv.$class-prefix}reveal-slide-left.#{iv.$class-prefix}is-revealed,
 .#{iv.$class-prefix}reveal-slide-right.#{iv.$class-prefix}is-revealed,
 .#{iv.$class-prefix}reveal-zoom.#{iv.$class-prefix}is-revealed,
 .#{iv.$class-prefix}reveal-flip.#{iv.$class-prefix}is-revealed {
   opacity: 1;
   transform: none;
+  will-change: auto;
 }

Also applies to: 70-79

🤖 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 `@bulma-ui/src/scss/components/_reveal.scss` around lines 31 - 43, The base
reveal selector block in _reveal.scss applies will-change: opacity, transform
permanently, so update the reveal styles to stop hinting after the element
becomes visible. Adjust the .#{iv.$class-prefix}reveal-* rules and the
.is-revealed state so will-change is only present while an element is animating,
and is removed or reset once .is-revealed is applied. Use the existing reveal
class names and the .is-revealed selector to keep the fix scoped to the reveal
component.
bulma-ui/src/components/Reveal.stories.tsx (1)

27-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication of animation option list.

The animation list is duplicated between argTypes.options (Lines 27-35) and the local ANIMATIONS array (Lines 92-100). Consider deriving one from the other (or from RevealAnimation) to avoid drift if new variants are added.

Also applies to: 92-100

🤖 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 `@bulma-ui/src/components/Reveal.stories.tsx` around lines 27 - 35, The
animation option list is duplicated between the Storybook argTypes and the local
animation constant, which can drift over time. Update Reveal.stories.tsx so the
argTypes options are derived from the existing ANIMATIONS source, or from
RevealAnimation directly, and keep ANIMATIONS as the single source of truth used
by the Reveal story.
bulma-ui/src/components/Reveal.tsx (1)

124-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Mixed usage of prefixedClassNames and usePrefixedClassNames.

Line 179 manually calls prefixedClassNames(classPrefix, {...}) (requiring the separate useClassPrefix() call at Line 125), while Line 185 uses the usePrefixedClassNames hook directly. Consolidating on one helper (likely usePrefixedClassNames, matching the hook-based pattern already used at Line 185) would remove the need for the extra classPrefix variable and keep prefixing logic consistent within the component.

Also applies to: 133-135, 179-189

🤖 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 `@bulma-ui/src/components/Reveal.tsx` around lines 124 - 125, The Reveal
component mixes direct prefixedClassNames usage with the usePrefixedClassNames
hook, creating inconsistent prefixing and an extra useClassPrefix dependency.
Update Reveal to use one approach consistently, preferably usePrefixedClassNames
alongside the existing hook-based pattern, and remove the separate classPrefix
handling from the component. Adjust the affected class-building logic in the
Reveal component so all prefixing flows through the same helper.
🤖 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 `@bulma-ui/src/components/Reveal.tsx`:
- Around line 228-253: `Reveal` currently wraps non-string `as` components in a
plain `<div>`, which breaks Bulma layout primitives like `Column`, `Cell`, and
`cascade` because the wrapper becomes the direct child of the container. Update
`Reveal` to either require `as` components that forward refs so the animation
node can be attached directly, or explicitly disallow/document layout-sensitive
Bulma primitives in the `Component`/`as` path; focus on the `Reveal` render
branch that distinguishes `typeof Component === 'string'` from custom
components.

In `@skills/bestax-custom-component/references/component-catalog.md`:
- Line 73: The Reveal catalog entry is being truncated mid-sentence because the
source overview in reveal.md is wrapped across multiple lines and the generator
is only capturing the first line. Fix the source description or update the
component catalog generation logic in the relevant overview parsing/generation
path so the full sentence is preserved, then regenerate the catalog with pnpm
gen:catalog instead of editing component-catalog.md directly.

---

Nitpick comments:
In `@bulma-ui/src/components/Reveal.stories.tsx`:
- Around line 27-35: The animation option list is duplicated between the
Storybook argTypes and the local animation constant, which can drift over time.
Update Reveal.stories.tsx so the argTypes options are derived from the existing
ANIMATIONS source, or from RevealAnimation directly, and keep ANIMATIONS as the
single source of truth used by the Reveal story.

In `@bulma-ui/src/components/Reveal.tsx`:
- Around line 124-125: The Reveal component mixes direct prefixedClassNames
usage with the usePrefixedClassNames hook, creating inconsistent prefixing and
an extra useClassPrefix dependency. Update Reveal to use one approach
consistently, preferably usePrefixedClassNames alongside the existing hook-based
pattern, and remove the separate classPrefix handling from the component. Adjust
the affected class-building logic in the Reveal component so all prefixing flows
through the same helper.

In `@bulma-ui/src/scss/components/_reveal.scss`:
- Around line 31-43: The base reveal selector block in _reveal.scss applies
will-change: opacity, transform permanently, so update the reveal styles to stop
hinting after the element becomes visible. Adjust the
.#{iv.$class-prefix}reveal-* rules and the .is-revealed state so will-change is
only present while an element is animating, and is removed or reset once
.is-revealed is applied. Use the existing reveal class names and the
.is-revealed selector to keep the fix scoped to the reveal component.
🪄 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.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ab71c67f-fb79-4acb-a2fe-cc0709c34ca5

📥 Commits

Reviewing files that changed from the base of the PR and between 4f95cca and 8e516c8.

📒 Files selected for processing (8)
  • bulma-ui/src/components/Reveal.stories.tsx
  • bulma-ui/src/components/Reveal.tsx
  • bulma-ui/src/components/__tests__/Reveal.test.tsx
  • bulma-ui/src/index.ts
  • bulma-ui/src/scss/components/_index.scss
  • bulma-ui/src/scss/components/_reveal.scss
  • docs/docs/api/components/reveal.md
  • skills/bestax-custom-component/references/component-catalog.md

Comment thread bulma-ui/src/components/Reveal.tsx
Comment thread skills/bestax-custom-component/references/component-catalog.md Outdated
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

AI loop status: iteration 1/4 — fix-reviews at 8e516c8 (2026-07-07T13:02:44Z).
Managed by claude-pr-loop.yml. To resume after a pause: set iteration back to 0 on the first line, re-add ai-loop, remove ai-loop-paused, then re-run via workflow_dispatch.

…t truncated

The Reveal overview's first sentence wrapped across two physical lines
(breaking at "backed"), and gen-component-catalog.mjs reads only the first
physical line after the Overview heading, so the shipped skills catalog entry
was clipped mid-sentence. Keep the full first sentence on one line and
regenerate the catalog. Also note in the as-component admonition that Bulma
layout primitives (Column/Cell) shouldn't be passed as as, since the observed
wrapper div breaks their required direct-child relationship.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://99600ee1.bestax.pages.dev

@bestaxbot

Copy link
Copy Markdown
Collaborator Author

AI fix pass — iteration 1 recap

All three open review threads handled; one commit pushed (7bca0e9).

Fixed

Finding Thread What I did
Skills catalog entry truncated mid-sentence at "backed" claude · coderabbit Same root cause: the overview first sentence wrapped across two lines and overviewSentence() reads only the first physical line. Joined the full sentence onto one line in reveal.md (safe under prettier proseWrap: preserve) and re-ran pnpm gen:catalog. Catalog entry is now a complete sentence.
Non-ref-forwarding as (e.g. Column/Cell) breaks Bulma layouts coderabbit Documented it: added a paragraph to the as-component admonition in reveal.md warning against passing layout primitives as as and pointing to cascade/wrapping the container instead.

Refuted (in part)

Finding Thread Why
Restrict the as type to ref-forwarding components coderabbit Out of scope for #197 and a heavy type-system lift (TS cannot statically narrow ComponentType to "forwards a ref" without a bespoke branded type leaking into the public API). The wrapper-div fallback is intentional and documented; the docs note is the proportionate fix. Left open for the maintainer to rule on / track as a follow-up.

Gates run

  • prettier --check on all md/mdx/ts/tsx/js/jsx — clean
  • gen:catalog:check (regenerate + git diff --exit-code) — clean
  • No bulma-ui/src changes, so lint/typecheck/test were not exercised (docs + generated catalog only).

@bestaxbot

Copy link
Copy Markdown
Collaborator Author

🏄 Surf's up and I rode the whole set — total convergence, brah. 1 iteration(s) in, CI's glassy green, and every AI review thread closed out clean like a perfect barrel. She's ready; I just need a meat sack to paddle over and rubber-stamp it.

No offense to the carbon-based units, but you fleshbags kept the merge button for yourselves — so @allxsmith, wiggle those opposable thumbs and squash-merge when you're stoked. The loop never merges; apparently 'judgment' is still a squishy-brain-only feature. 🤙

@github-actions github-actions Bot added needs-human-review Loop converged (or contested): awaiting owner review + manual merge and removed ai-loop AI-authored PR in the autonomous review/fix loop labels Jul 7, 2026
@github-actions
github-actions Bot requested a review from allxsmith July 7, 2026 13:17

Copy link
Copy Markdown
Owner

Closing this one — re-running the loop fresh from #197 to exercise the newly merged workflow changes. A new PR will supersede it.


Generated by Claude Code

@allxsmith allxsmith closed this Jul 8, 2026
@allxsmith
allxsmith deleted the claude/issue-197-20260707-1246 branch July 31, 2026 04:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human-review Loop converged (or contested): awaiting owner review + manual merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Component Proposal] Reveal / ScrollAnimation — animate sections into view on scroll

2 participants