Skip to content

[feat]: bundler tabs#943

Merged
schiller-manuel merged 1 commit into
mainfrom
bundler-tabs-neww
May 24, 2026
Merged

[feat]: bundler tabs#943
schiller-manuel merged 1 commit into
mainfrom
bundler-tabs-neww

Conversation

@LadyBluenotes
Copy link
Copy Markdown
Member

@LadyBluenotes LadyBluenotes commented May 24, 2026

Summary by CodeRabbit

  • New Features

    • Added bundler tabs to documentation, enabling users to switch between bundler-specific code examples (Vite and Rsbuild).
    • Bundler selection persists across sessions and synchronizes across all bundler tab blocks.
  • Documentation

    • Added "Bundler tabs" documentation section with usage examples and supported bundlers reference.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

Introduces bundler-specific tabbed documentation blocks by adding a persisted enum store utility, bundler type definitions, and a new BundlerTabs React component that syncs user bundler selection across the page. Updates markdown plugins to extract bundler panels from headings, refactors existing package manager tabs to use the new persistence pattern, and enhances tab component infrastructure with data-driven layout styling.

Changes

Bundler tabs feature

Layer / File(s) Summary
Bundler utilities and enum patterns
src/utils/markdown/bundler.ts, src/utils/markdown/installCommand.ts, src/utils/markdown/plugins/helpers.ts
Defines BUNDLERS constants, Bundler union type, isBundler type guard, and refactors PACKAGE_MANAGERS to tuple-based enum pattern with isPackageManager validation.
Persisted enum store factory
src/components/markdown/usePersistedEnumStore.ts
Generic Zustand store that persists string enum values to localStorage with deferred hydration via useHydrate hook to avoid SSR/CSR mismatches.
Tab component infrastructure updates
src/components/markdown/Tabs.tsx, src/components/markdown/FileTabs.tsx, src/components/markdown/CodeBlockView.tsx
Adds optional panelContent prop to Tabs for data-driven layout, refactors tab visibility from hidden attribute to flex/hidden CSS classes, updates FileTabs and CodeBlockView styling.
PackageManagerTabs persisted store migration
src/components/markdown/PackageManagerTabs.tsx
Refactors to use new createPersistedEnumStore pattern, replacing manual localStorage with validated enum persistence and hydration.
BundlerTabs component and markdown integration
src/components/markdown/BundlerTabs.tsx, src/components/markdown/MdComponents.tsx, src/components/markdown/index.ts
Implements BundlerTabs component that tracks bundler selection, renders bundler-specific panels, integrates with MdComponents to wire bundler metadata through markdown rendering pipeline.
Markdown plugin bundler tab extraction
src/utils/markdown/plugins/transformTabsComponent.ts
Adds bundler variant support with extractBundlerData function, improves hast type safety with Element types, enhances code block title extraction with multiple property key fallbacks.
Styling and documentation
src/styles/app.css, docs-info.md
Updates CSS for [data-content] attribute-based tab layouts distinguishing code-only vs mixed panels, adds typography rules, documents bundler tabs feature with persistence behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 New bundler tabs hop into the frame,
Vite and Rsbuild, both stake their claim,
localStorage remembers your choice with care,
A persisted enum store, clean and fair,
Markdown panels rise, perfectly aligned! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[feat]: bundler tabs' directly and specifically describes the primary feature addition in this changeset—a new bundler tabs component and supporting infrastructure.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bundler-tabs-neww

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/markdown/plugins/transformTabsComponent.ts (1)

40-58: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden data-attributes schema validation before returning parsed values.

JSON.parse succeeding does not guarantee string-valued attributes. For example, {"variant":1} passes parsing but can throw later at Line 334 when calling .toLowerCase(). Return only string key/value pairs (or {}) to keep this transform resilient to malformed markdown metadata.

Suggested fix
 function parseAttributes(node: HastNode): Record<string, string> {
   const rawAttributes = node.properties?.['data-attributes']
   if (typeof rawAttributes === 'string') {
     try {
-      return JSON.parse(rawAttributes)
+      const parsed: unknown = JSON.parse(rawAttributes)
+      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+        return {}
+      }
+      return Object.fromEntries(
+        Object.entries(parsed).filter(
+          (entry): entry is [string, string] => typeof entry[1] === 'string',
+        ),
+      )
     } catch (error) {
       if (import.meta.env?.DEV) {
         // eslint-disable-next-line no-console
         console.warn(
           '[transformTabsComponent] Failed to parse data-attributes JSON:',
🤖 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 `@src/utils/markdown/plugins/transformTabsComponent.ts` around lines 40 - 58,
The parseAttributes function currently returns whatever JSON.parse produces,
which may include non-string values and later cause errors (e.g., calling
.toLowerCase()); update parseAttributes to, after successful JSON.parse of
node.properties['data-attributes'], validate the result is an object and create
a new object that includes only keys whose values are strings (discard others),
then return that filtered Record<string,string> (keep the existing try/catch and
DEV console.warn behavior on parse failure); reference parseAttributes and
node.properties['data-attributes'] when locating the change.
🧹 Nitpick comments (5)
src/styles/app.css (1)

1210-1215: ⚡ Quick win

Consider using a data attribute for the title wrapper instead of relying on DOM structure.

The nested > div:first-child > div:first-child selector is fragile and will break if the component DOM structure changes. A data attribute like [data-codeblock-title] would be more maintainable and self-documenting.

♻️ More resilient alternative

In the component that renders the codeblock title wrapper, add a data attribute:

<div data-codeblock-title>
  {/* title content */}
</div>

Then update the CSS:

-[data-tab][data-content='code-only']
-  .codeblock
-  > div:first-child
-  > div:first-child {
+[data-tab][data-content='code-only'] .codeblock [data-codeblock-title] {
   `@apply` hidden;
 }
🤖 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 `@src/styles/app.css` around lines 1210 - 1215, The CSS rule using the fragile
selector "[data-tab][data-content='code-only'] .codeblock > div:first-child >
div:first-child" should be replaced by a data-attribute on the title wrapper to
avoid DOM-structure coupling; add a data attribute (e.g. data-codeblock-title)
to the element that wraps the codeblock title in the component and update the
stylesheet to target "[data-tab][data-content='code-only'] .codeblock
[data-codeblock-title]" (or simply "[data-codeblock-title]") and apply the same
hidden styles so the rule references the stable attribute instead of chained
:first-child selectors.
src/components/markdown/BundlerTabs.tsx (2)

63-67: ⚡ Quick win

Simplify fallback logic.

The current fallback chain tabDefinitions[0]?.slug ?? activeBundler will use activeBundler as the final fallback even when it's not present in tabDefinitions. This could pass an invalid slug to the Tabs component when tabDefinitions is empty.

Since line 78 guards against empty tabDefinitions, the fallback will only execute when there's at least one tab. Consider simplifying:

♻️ Proposed refactor
  const resolvedActiveSlug = tabDefinitions.some(
    (tab) => tab.slug === activeBundler,
  )
    ? activeBundler
-   : (tabDefinitions[0]?.slug ?? activeBundler)
+   : tabDefinitions[0]!.slug

The non-null assertion is safe here because line 78's early return guarantees tabDefinitions.length > 0.

🤖 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 `@src/components/markdown/BundlerTabs.tsx` around lines 63 - 67,
resolvedActiveSlug fallback can incorrectly use activeBundler when
tabDefinitions is empty; since the component already returns early when
tabDefinitions.length === 0 (see the guard above), simplify the fallback to use
the first tab's slug directly. Update the resolvedActiveSlug calculation to
select activeBundler if present in tabDefinitions, otherwise use
tabDefinitions[0].slug (with a non-null assertion or guaranteed access) so an
invalid slug is not passed to Tabs; reference the variables resolvedActiveSlug,
tabDefinitions, activeBundler and the Tabs component when making the change.

38-44: ⚡ Quick win

Consider memoization stability.

The childrenArray dependency is derived from React.Children.toArray(children) (line 36), which creates a new array reference on every render when children changes. This causes the panelsBySlug memo to recompute even when the underlying children content hasn't changed.

Since children is already in scope, consider using children directly as the dependency instead of childrenArray, or move the toArray call inside the useMemo block.

♻️ Proposed refactor
-  const childrenArray = React.Children.toArray(children)
-
   const panelsBySlug = React.useMemo(() => {
+    const childrenArray = React.Children.toArray(children)
     const map = new Map<string, React.ReactNode>()
     tabs.forEach((tab, index) => {
       map.set(tab.slug, childrenArray[index])
     })
     return map
-  }, [tabs, childrenArray])
+  }, [tabs, children])
🤖 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 `@src/components/markdown/BundlerTabs.tsx` around lines 38 - 44, The memo for
panelsBySlug is unstable because it depends on childrenArray (created via
React.Children.toArray) which changes identity each render; fix by either moving
React.Children.toArray(children) inside the React.useMemo callback and keeping
[tabs, children] as dependencies, or keep childrenArray but replace it with
children in the dependency array. Update the panelsBySlug logic
(map.set(tab.slug, ...)) to use the locally created array inside the memo (if
moving toArray) or continue using childrenArray but ensure useMemo deps are
[tabs, children] so recomputation only occurs when actual children change.
src/components/markdown/MdComponents.tsx (2)

152-152: 💤 Low value

Remove or clarify the void index statement.

The void index statement on line 152 and its comment don't serve a clear purpose. The comment mentions "preserve insertion order for tabs that came in without metadata," but the code doesn't implement any order-preserving logic—index is neither used nor stored.

Consider removing both the statement and comment, or clarifying what ordering guarantee is intended.

♻️ Suggested cleanup
        childrenBySlug.set(slug, panel.props.children)
-       // Preserve insertion order for tabs that came in without metadata
-       void index
      })
🤖 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 `@src/components/markdown/MdComponents.tsx` at line 152, The stray "void index"
expression and its comment in MdComponents.tsx (referring to index) do nothing
and should be removed or made meaningful: either delete the "void index" line
and the accompanying comment, or if you intended to preserve insertion order for
tabs without metadata, capture and use that index (e.g., attach an
insertionIndex field when building the tab objects and use it as a fallback key
in the sorting/ordering logic such as in the code that renders or sorts tabs) so
the comment matches implemented behavior; update references to use
insertionIndex instead of the unused index variable.

207-209: Potentially unused data-tab-index in MdTabPanelProps

data-tab-index is written on md-tab-panel elements by src/utils/markdown/plugins/transformTabsComponent.ts, but in src/components/markdown/MdComponents.tsx it’s only present in the MdTabPanelProps type and never read (MdTabPanel just renders children; data-tab-slug is the one consumed). Either remove data-tab-index from MdTabPanelProps or document why it’s intentionally kept for future use.

🤖 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 `@src/components/markdown/MdComponents.tsx` around lines 207 - 209,
MdTabPanelProps declares 'data-tab-index' but MdTabPanel never reads it
(MdTabPanel renders children and only consumes 'data-tab-slug'), so either
remove 'data-tab-index' from the MdTabPanelProps type or add an explicit
comment/docs indicating it's intentionally reserved; update the MdTabPanelProps
declaration in MdComponents.tsx to drop 'data-tab-index' if unused, or add a
clear comment on the property and keep it only if transformTabsComponent.ts
relies on writing it for external tooling; ensure you update any related
tests/types and keep references to MdTabPanel and the transformTabsComponent.ts
behavior consistent.
🤖 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.

Outside diff comments:
In `@src/utils/markdown/plugins/transformTabsComponent.ts`:
- Around line 40-58: The parseAttributes function currently returns whatever
JSON.parse produces, which may include non-string values and later cause errors
(e.g., calling .toLowerCase()); update parseAttributes to, after successful
JSON.parse of node.properties['data-attributes'], validate the result is an
object and create a new object that includes only keys whose values are strings
(discard others), then return that filtered Record<string,string> (keep the
existing try/catch and DEV console.warn behavior on parse failure); reference
parseAttributes and node.properties['data-attributes'] when locating the change.

---

Nitpick comments:
In `@src/components/markdown/BundlerTabs.tsx`:
- Around line 63-67: resolvedActiveSlug fallback can incorrectly use
activeBundler when tabDefinitions is empty; since the component already returns
early when tabDefinitions.length === 0 (see the guard above), simplify the
fallback to use the first tab's slug directly. Update the resolvedActiveSlug
calculation to select activeBundler if present in tabDefinitions, otherwise use
tabDefinitions[0].slug (with a non-null assertion or guaranteed access) so an
invalid slug is not passed to Tabs; reference the variables resolvedActiveSlug,
tabDefinitions, activeBundler and the Tabs component when making the change.
- Around line 38-44: The memo for panelsBySlug is unstable because it depends on
childrenArray (created via React.Children.toArray) which changes identity each
render; fix by either moving React.Children.toArray(children) inside the
React.useMemo callback and keeping [tabs, children] as dependencies, or keep
childrenArray but replace it with children in the dependency array. Update the
panelsBySlug logic (map.set(tab.slug, ...)) to use the locally created array
inside the memo (if moving toArray) or continue using childrenArray but ensure
useMemo deps are [tabs, children] so recomputation only occurs when actual
children change.

In `@src/components/markdown/MdComponents.tsx`:
- Line 152: The stray "void index" expression and its comment in
MdComponents.tsx (referring to index) do nothing and should be removed or made
meaningful: either delete the "void index" line and the accompanying comment, or
if you intended to preserve insertion order for tabs without metadata, capture
and use that index (e.g., attach an insertionIndex field when building the tab
objects and use it as a fallback key in the sorting/ordering logic such as in
the code that renders or sorts tabs) so the comment matches implemented
behavior; update references to use insertionIndex instead of the unused index
variable.
- Around line 207-209: MdTabPanelProps declares 'data-tab-index' but MdTabPanel
never reads it (MdTabPanel renders children and only consumes 'data-tab-slug'),
so either remove 'data-tab-index' from the MdTabPanelProps type or add an
explicit comment/docs indicating it's intentionally reserved; update the
MdTabPanelProps declaration in MdComponents.tsx to drop 'data-tab-index' if
unused, or add a clear comment on the property and keep it only if
transformTabsComponent.ts relies on writing it for external tooling; ensure you
update any related tests/types and keep references to MdTabPanel and the
transformTabsComponent.ts behavior consistent.

In `@src/styles/app.css`:
- Around line 1210-1215: The CSS rule using the fragile selector
"[data-tab][data-content='code-only'] .codeblock > div:first-child >
div:first-child" should be replaced by a data-attribute on the title wrapper to
avoid DOM-structure coupling; add a data attribute (e.g. data-codeblock-title)
to the element that wraps the codeblock title in the component and update the
stylesheet to target "[data-tab][data-content='code-only'] .codeblock
[data-codeblock-title]" (or simply "[data-codeblock-title]") and apply the same
hidden styles so the rule references the stable attribute instead of chained
:first-child selectors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8446910c-af0c-4748-8799-84d6108434f4

📥 Commits

Reviewing files that changed from the base of the PR and between 8735dde and 46e516d.

📒 Files selected for processing (14)
  • docs-info.md
  • src/components/markdown/BundlerTabs.tsx
  • src/components/markdown/CodeBlockView.tsx
  • src/components/markdown/FileTabs.tsx
  • src/components/markdown/MdComponents.tsx
  • src/components/markdown/PackageManagerTabs.tsx
  • src/components/markdown/Tabs.tsx
  • src/components/markdown/index.ts
  • src/components/markdown/usePersistedEnumStore.ts
  • src/styles/app.css
  • src/utils/markdown/bundler.ts
  • src/utils/markdown/installCommand.ts
  • src/utils/markdown/plugins/helpers.ts
  • src/utils/markdown/plugins/transformTabsComponent.ts

@schiller-manuel schiller-manuel merged commit 320867c into main May 24, 2026
9 checks passed
@schiller-manuel schiller-manuel deleted the bundler-tabs-neww branch May 24, 2026 19:12
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