Skip to content

feat(content-sidebar): implement for custom sidebar panels#4326

Merged
mergify[bot] merged 23 commits intomasterfrom
custom-sidebar-panels
Feb 6, 2026
Merged

feat(content-sidebar): implement for custom sidebar panels#4326
mergify[bot] merged 23 commits intomasterfrom
custom-sidebar-panels

Conversation

@fpan225
Copy link
Contributor

@fpan225 fpan225 commented Oct 6, 2025

Summary

  • New Features

    • Added configurable custom sidebar panels and exported panel types for integrations.
  • UI / Behavior

    • Sidebar shows custom panels alongside native AI panel, with updated ordering, visibility rules, and default-panel precedence.
  • Tests

    • Expanded test coverage for custom panels, native AI interactions, ordering, accessibility, navigation, and edge cases.
  • Documentation / Stories

    • New visual stories demonstrating custom panel combinations, defaults, and interactive scenarios.

snapshot

image

Summary by CodeRabbit

  • New Features

    • Add support for custom sidebar panels (icons, titles, content) that integrate with native sidebars (including Box AI), affect ordering/default selection, refresh behavior, and accessibility/ARIA; Sidebar components now accept a customSidebarPanels prop.
    • Navigation and callbacks accept broader view identifiers (string IDs) for custom tabs.
  • Tests

    • Expanded unit and visual-story coverage for custom panels and Box AI: ordering, precedence, icons, ARIA/tooltips, refresh, navigation, and multi-panel scenarios.

@fpan225 fpan225 requested review from a team as code owners October 6, 2025 17:41
@CLAassistant
Copy link

CLAassistant commented Oct 6, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/elements/content-sidebar/SidebarNav.js (1)

82-105: Make that Box AI focus check respect custom paths, fool!

Right now the click handler only fires focusPrompt() when sidebarview === SIDEBAR_VIEW_BOXAI. With the new API, the Box AI panel can ship with an alternate path, so the button will stop focusing the prompt even though the tab ID is still boxai. I pity the fool who ships a regression like that! Key the check off the actual Box AI tab metadata so custom paths still trigger the focus.

-        if (sidebarview === SIDEBAR_VIEW_BOXAI) {
+        if (boxAiTab && sidebarview === boxAiTab.path) {
             focusPrompt();
         }
src/elements/content-sidebar/SidebarPanels.js (1)

146-156: Remove unused Box AI sidebar cache and ref, fool
I pity the fool who lets dead code linger: boxAiSidebarCache is never accessed, and the boxAISidebar ref + refresh block are redundant with customSidebars. Drop the state field (lines 146–156), remove the ref & its refresh() (lines 209–212), and update SidebarPanels.test.js to remove 'boxAISidebar' from the sidebar list and assertions.

🧹 Nitpick comments (5)
src/elements/content-sidebar/SidebarPanels.js (4)

327-360: Drop redundant component existence checks; Flow’s got your back, fool!

CustomSidebarPanel.component is required by type. Remove !CustomPanelComponent guard and the ternary. Cleaner, less noise.

Apply:

-                        if (isDisabled || !CustomPanelComponent) {
+                        if (isDisabled) {
                             return null;
                         }

                         return (
                             <Route
                                 exact
                                 key={customPanelId}
                                 path={`/${customPanelPath}`}
                                 render={() => {
                                     this.handlePanelRender(customPanelPath);
-                                    return CustomPanelComponent ? (
-                                        <CustomPanelComponent
-                                            elementId={elementId}
-                                            key={file.id}
-                                            fileExtension={file.extension}
-                                            hasSidebarInitialized={isInitialized}
-                                            ref={this.getCustomSidebarRef(customPanelId)}
-                                        />
-                                    ) : null;
+                                    return (
+                                        <CustomPanelComponent
+                                            elementId={elementId}
+                                            key={file.id}
+                                            fileExtension={file.extension}
+                                            hasSidebarInitialized={isInitialized}
+                                            ref={this.getCustomSidebarRef(customPanelId)}
+                                        />
+                                    );
                                 }}
                             />
                         );

Based on learnings


186-196: Refs won’t attach to function components without forwardRef — don’t get played, sucka!

You pass ref={this.getCustomSidebarRef(customPanelId)} to arbitrary custom components. Unless they use React.forwardRef (and ideally useImperativeHandle to expose refresh()), ref.current stays null and your custom refresh loop won’t hit them. Add a note to the CustomSidebarPanel contract/docs requiring forwardRef for refresh support, or switch to a callback/imperative prop.

Also applies to: 349-355


237-254: Panel ordering logic looks right; Box AI included when not default — solid work, fool.

Else-branch now includes all custom panels (including Box AI). Nice fix versus earlier exclusion. One caveat: if a custom panel path collides with a built-in (e.g., “metadata”), it can override eligibility/order. Add a reserved-name guard or validate uniqueness of panel.path.

Example guard (conceptual):

  • Reserved: {docgen, skills, activity, details, metadata}
  • Warn or skip any custom with a reserved path

292-308: Avoid key collisions in eligibility map, jive turkey!

Keys for custom eligibility are paths that can overwrite built-ins if they collide. Consider validating custom path against reserved names before merging, or namespace custom keys to prevent accidental overrides.

src/elements/content-sidebar/__tests__/SidebarPanels.test.js (1)

663-819: Consider a refresh test for custom panels — show me the receipts, sucka!

Add a test where a custom panel uses forwardRef to expose refresh(), mount, call instance.refresh(), and assert your custom ref’s refresh is called via the customSidebars loop.

I can draft the test with a forwardRef custom component and verify refresh invocation. Want me to add it?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6937b60 and 59c10df.

📒 Files selected for processing (11)
  • src/elements/content-sidebar/ContentSidebar.js (4 hunks)
  • src/elements/content-sidebar/Sidebar.js (6 hunks)
  • src/elements/content-sidebar/SidebarNav.js (4 hunks)
  • src/elements/content-sidebar/SidebarPanels.js (8 hunks)
  • src/elements/content-sidebar/__tests__/SidebarNav.test.js (4 hunks)
  • src/elements/content-sidebar/__tests__/SidebarPanels.test.js (19 hunks)
  • src/elements/content-sidebar/flowTypes.js (2 hunks)
  • src/elements/content-sidebar/stories/BoxAISideBar.mdx (0 hunks)
  • src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx (0 hunks)
  • src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx (0 hunks)
  • src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx (2 hunks)
💤 Files with no reviewable changes (3)
  • src/elements/content-sidebar/stories/BoxAISidebar.stories.tsx
  • src/elements/content-sidebar/stories/tests/BoxAISidebar-visual.stories.tsx
  • src/elements/content-sidebar/stories/BoxAISideBar.mdx
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-03T18:30:44.447Z
Learnt from: fpan225
PR: box/box-ui-elements#4239
File: src/elements/content-sidebar/SidebarPanels.js:0-0
Timestamp: 2025-09-03T18:30:44.447Z
Learning: In the CustomSidebarPanel type, the component field is required (React.ComponentType<any>), so runtime checks for component existence are unnecessary since Flow will catch missing components at compile time. User fpan225 prefers to rely on the type system rather than adding redundant runtime checks.

Applied to files:

  • src/elements/content-sidebar/SidebarPanels.js
  • src/elements/content-sidebar/Sidebar.js
  • src/elements/content-sidebar/flowTypes.js
  • src/elements/content-sidebar/ContentSidebar.js
  • src/elements/content-sidebar/SidebarNav.js
📚 Learning: 2025-09-23T21:14:20.232Z
Learnt from: fpan225
PR: box/box-ui-elements#4239
File: src/elements/content-sidebar/SidebarPanels.js:0-0
Timestamp: 2025-09-23T21:14:20.232Z
Learning: User fpan225 clarified that Box AI is included in customPanels if it exists, but the current getPanelOrder function logic in the else branch still excludes Box AI from the returned order when shouldBoxAIBeDefaultPanel is false, since otherCustomPanelPaths specifically filters out Box AI panels.

Applied to files:

  • src/elements/content-sidebar/SidebarPanels.js
  • src/elements/content-sidebar/__tests__/SidebarPanels.test.js
📚 Learning: 2025-06-17T13:30:02.172Z
Learnt from: rafalmaksymiuk
PR: box/box-ui-elements#4144
File: src/elements/content-sidebar/versions/VersionsList.js:24-33
Timestamp: 2025-06-17T13:30:02.172Z
Learning: In the box-ui-elements codebase, Flow components use .flow.js type definition files, not TypeScript .ts files. The InternalSidebarNavigation type is a union type where different variants may have different properties like versionId, and proper type safety is ensured through conditional checks in methods like getSelectedVersionId.

Applied to files:

  • src/elements/content-sidebar/Sidebar.js
  • src/elements/content-sidebar/flowTypes.js
  • src/elements/content-sidebar/ContentSidebar.js
📚 Learning: 2025-09-03T18:24:37.905Z
Learnt from: fpan225
PR: box/box-ui-elements#4239
File: src/elements/content-sidebar/SidebarPanels.js:0-0
Timestamp: 2025-09-03T18:24:37.905Z
Learning: User fpan225 prefers to keep the original `customPanels` variable name rather than normalizing it to a different variable name (like `panels`) for better code readability. They use `if (hasCustomPanels && customPanels)` pattern to satisfy Flow type checking instead of creating a normalized array.

Applied to files:

  • src/elements/content-sidebar/__tests__/SidebarPanels.test.js
📚 Learning: 2025-07-11T14:43:02.677Z
Learnt from: jpan-box
PR: box/box-ui-elements#4166
File: src/elements/content-sidebar/SidebarNav.js:126-126
Timestamp: 2025-07-11T14:43:02.677Z
Learning: In the box-ui-elements repository, there's a file-type-based pattern for internationalization: TypeScript files (.tsx) predominantly use the modern useIntl hook (41 vs 15 files), while JavaScript files (.js) predominantly use the legacy injectIntl HOC (64 vs 5 files). New TypeScript components should use useIntl, while existing JavaScript components typically continue using injectIntl for consistency.

Applied to files:

  • src/elements/content-sidebar/SidebarNav.js
🧬 Code graph analysis (4)
src/elements/content-sidebar/SidebarPanels.js (2)
src/elements/content-sidebar/ContentSidebar.js (4)
  • Props (226-226)
  • Props (275-275)
  • Props (320-320)
  • Props (352-385)
src/elements/content-sidebar/Sidebar.js (4)
  • Props (294-319)
  • hasSkills (324-324)
  • hasActivity (321-321)
  • hasMetadata (323-323)
src/elements/content-sidebar/__tests__/SidebarNav.test.js (1)
src/elements/content-sidebar/SidebarNav.js (1)
  • boxAiTab (90-90)
src/elements/content-sidebar/__tests__/SidebarPanels.test.js (1)
src/elements/content-sidebar/Sidebar.js (1)
  • hasBoxAI (326-328)
src/elements/content-sidebar/SidebarNav.js (4)
src/elements/content-sidebar/ContentSidebar.js (4)
  • Props (226-226)
  • Props (275-275)
  • Props (320-320)
  • Props (352-385)
src/elements/content-sidebar/Sidebar.js (5)
  • Props (294-319)
  • hasActivity (321-321)
  • hasDetails (322-322)
  • hasSkills (324-324)
  • hasMetadata (323-323)
src/elements/content-sidebar/SidebarNavButton.js (1)
  • SidebarNavButton (32-32)
src/elements/common/interactionTargets.js (2)
  • SIDEBAR_NAV_TARGETS (2-11)
  • SIDEBAR_NAV_TARGETS (2-11)
🪛 Biome (2.1.2)
src/elements/content-sidebar/SidebarPanels.js

[error] 44-44: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 136-136: return types can only be used in TypeScript files

remove this type annotation

(parse)


[error] 186-186: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 186-186: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 237-237: optional parameters are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 237-237: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 237-237: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 237-237: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 310-310: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)

src/elements/content-sidebar/Sidebar.js

[error] 31-31: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 32-32: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 33-33: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 34-34: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 35-35: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 37-37: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)

src/elements/content-sidebar/flowTypes.js

[error] 45-46: type alias are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)

src/elements/content-sidebar/ContentSidebar.js

[error] 49-49: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)

src/elements/content-sidebar/SidebarNav.js

[error] 32-32: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 33-33: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 35-35: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint_test_build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Summary

@fpan225 fpan225 force-pushed the custom-sidebar-panels branch from 59c10df to df9a123 Compare October 6, 2025 23:11
@fpan225 fpan225 changed the title feat(content-sidebar): implement for custom sidebar panels (BREAKING … feat(content-sidebar): implement for custom sidebar panels Oct 14, 2025
@fpan225 fpan225 force-pushed the custom-sidebar-panels branch from df9a123 to 595bd11 Compare January 21, 2026 03:03
@box box deleted a comment from coderabbitai bot Jan 21, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Walkthrough

Adds a new Flow type CustomSidebarPanel and threads a customSidebarPanels prop from ContentSidebar → Sidebar → SidebarNav & SidebarPanels; integrates custom panels into tab construction, ordering, BoxAI detection, per-panel refs/refresh, tests, and stories.

Changes

Cohort / File(s) Summary
Type Definitions
src/elements/content-sidebar/flowTypes.js
Add CustomSidebarPanel and NavButtonOverrideProps types and export CustomSidebarPanel.
Top-level Prop Threading
src/elements/content-sidebar/ContentSidebar.js, src/elements/content-sidebar/Sidebar.js
Add customSidebarPanels?: Array<CustomSidebarPanel> to props and forward from ContentSidebar → Sidebar; import SIDEBAR_VIEW_BOXAI and distinguish native vs custom BoxAI.
Nav Rendering / Buttons
src/elements/content-sidebar/SidebarNav.js, src/elements/content-sidebar/SidebarNavButton.js
Introduce renderCustomPanelIcon, accept customSidebarPanels, rename hasBoxAIhasNativeBoxAISidebar, build visible tabs by merging native and custom panels; widen SidebarNavButton types to accept `ViewTypeValues
Panels Routing / Rendering
src/elements/content-sidebar/SidebarPanels.js
Add customSidebarPanels prop, customSidebars: Map, getCustomSidebarRef(), getPanelOrder(); refactor default/redirect/refresh logic to include custom panels and BoxAI interplay.
Tests — Nav & Panels
src/elements/content-sidebar/__tests__/SidebarNav.test.js, src/elements/content-sidebar/__tests__/SidebarPanels.test.js
Large test additions/rewrites covering ordering, precedence, activation, refresh, icon rendering, ARIA/tooltips, disabled cases, and multiple custom panels.
Tests — Utils
src/elements/content-sidebar/__tests__/SidebarUtils.test.js
Update canHaveBoxAISidebar() tests to use feature-flag presence rather than hasBoxAI property.
Stories / Visuals
src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx
Add mock custom panels/icons, new stories covering native/custom BoxAI interactions, feature-flag permutations, and play functions for ARIA/visual checks.
Common Types
src/elements/common/types/SidebarNavigation.js.flow
Widen sidebar field type from ViewTypeValues to `ViewTypeValues

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant ContentSidebar as ContentSidebar
  participant Sidebar as Sidebar
  participant SidebarNav as SidebarNav
  participant SidebarPanels as SidebarPanels

  User->>ContentSidebar: mount(props { customSidebarPanels? })
  ContentSidebar->>Sidebar: render(customSidebarPanels)
  Sidebar->>SidebarNav: render(customSidebarPanels, hasNativeBoxAISidebar)
  Sidebar->>SidebarPanels: render(customSidebarPanels, hasNativeBoxAISidebar)
  SidebarNav->>SidebarNav: compute visibleTabs(hasNativeBoxAISidebar, customSidebarPanels)
  SidebarPanels->>SidebarPanels: compute panelOrder(defaults, customSidebarPanels, hasNativeBoxAISidebar)
  User->>SidebarNav: click tab(panelId)
  SidebarNav->>SidebarPanels: request showPanel(panelId)
  SidebarPanels-->>User: render selected panel content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • greg-in-a-box
  • bfoxx1906
  • jpan-box

Poem

🐇 I hop from tab to tab with glee,
New panels bloom where none could be,
Native BoxAI or custom art,
I press a button — panels start,
Carrots, icons, and tidy props — whee! 🎋

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is incomplete; it only includes generic merge instructions from the template without addressing the actual changes, objectives, or implementation details required. Replace the template merge instructions with a meaningful description covering: what custom sidebar panels are, how to use them, key implementation details, and relevant context for reviewers.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately and concisely describes the main feature addition of implementing custom sidebar panels, which is the primary objective of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch custom-sidebar-panels

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":404,"request":{"method":"PATCH","url":"https://api.github.com/repos/box/box-ui-elements/issues/comments/3373043675","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\nAdds public support for custom sidebar panels/tabs via new Flow types and props (`customSidebarPanels`/`customTabs`/`customPanels`), wires them through ContentSidebar → Sidebar → SidebarNav/SidebarPanels, makes BoxAI detection data-driven, updates tests for custom panels, and removes BoxAI-specific Storybook stories.\n\n## Changes\n| Cohort / File(s) | Summary |\n| --- | --- |\n| **Public API: ContentSidebar prop**<br>`src/elements/content-sidebar/ContentSidebar.js` | Adds `customSidebarPanels?: Array<CustomSidebarPanel>` prop; imports `CustomSidebarPanel`; forwards prop to `Sidebar`. |\n| **Sidebar core wiring**<br>`src/elements/content-sidebar/Sidebar.js` | Accepts `customSidebarPanels?: Array<CustomSidebarPanel>`; derives native vs custom BoxAI presence; forwards `customTabs`/`customPanels` to `SidebarNav`/`SidebarPanels`; updates CSS class condition for wider layout. |\n| **Navigation tabs logic**<br>`src/elements/content-sidebar/SidebarNav.js` | New `customTabs?: Array<CustomSidebarPanel>` prop; builds `visibleTabs` by combining default tabs and `customTabs` (handles BoxAI via id); replaces hard-coded tab blocks with dynamic rendering. |\n| **Panels routing and order**<br>`src/elements/content-sidebar/SidebarPanels.js` | New `customPanels?: Array<CustomSidebarPanel>` prop; adds `getPanelOrder`/`DEFAULT_SIDEBAR_VIEWS`, lazy refs for custom panels, routing insertion for custom panels, and redirect logic respecting custom/native BoxAI. |\n| **Flow types**<br>`src/elements/content-sidebar/flowTypes.js` | Adds and exports `CustomSidebarPanel` and `CustomSidebarPanels` Flow types (id, path, component, title, optional props). |\n| **Unit tests: nav**<br>`src/elements/content-sidebar/__tests__/SidebarNav.test.js` | Tests updated/expanded for `customTabs`, BoxAI priority (native vs custom), ordering, icons, disabled/aria states, DOM order, and `onPanelChange`. |\n| **Unit tests: panels**<br>`src/elements/content-sidebar/__tests__/SidebarPanels.test.js` | Tests updated to pass `customPanels`; adds BoxAI panel helper; coverage for multiple custom panels, ordering, disabled handling, fallbacks, and navigation behavior. |\n| **Storybook removals**<br>`src/elements/content-sidebar/stories/BoxAISideBar.mdx`, `.../stories/BoxAISidebar.stories.tsx`, `.../stories/tests/BoxAISidebar-visual.stories.tsx` | Removes BoxAI-specific docs, stories, and visual-test story files/exports. |\n| **Storybook consolidation**<br>`src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx` | Replaces BoxAI-focused variants with a single `ContentSidebar` story; removes BoxAI feature toggles; updates imports/types and exported story name. |\n\n## Sequence Diagram(s)\n```mermaid\nsequenceDiagram\n  autonumber\n  actor App\n  participant ContentSidebar as \"ContentSidebar\\n(renders)\"\n  participant Sidebar as \"Sidebar\\n(wires props)\"\n  participant SidebarNav as \"SidebarNav\\n(tabs)\"\n  participant SidebarPanels as \"SidebarPanels\\n(routes/panels)\"\n\n  App->>ContentSidebar: render({ customSidebarPanels })\n  ContentSidebar->>Sidebar: render({ customSidebarPanels })\n  Sidebar->>Sidebar: determine hasNativeBoxAI, hasCustomBoxAI\n  Sidebar->>SidebarNav: render({ customTabs: customSidebarPanels })\n  Sidebar->>SidebarPanels: render({ customPanels: customSidebarPanels })\n\n  SidebarNav->>SidebarNav: build visibleTabs = defaults + customTabs (filter/order)\n  SidebarPanels->>SidebarPanels: compute panelOrder(defaults + customPanels)\n  SidebarPanels->>SidebarPanels: register routes & refs for custom panels\n  SidebarPanels-->>App: render/redirect to first eligible panel\n```\n\n## Estimated code review effort\n🎯 4 (Complex) | ⏱️ ~60 minutes\n\n## Possibly related PRs\n- box/box-ui-elements#4239: Implements same custom sidebar panels feature and wiring across ContentSidebar/Sidebar/Nav/Panels — strong overlap.\n- box/box-ui-elements#4320: Reverts the customSidebarPanels changes and restores prior hasBoxAI behavior — directly touches same files.\n- box/box-ui-elements#4370: Modifies SidebarNav tab-rendering logic and overflow behavior — related to tab rendering changes here.\n\n## Suggested labels\n`ready-to-merge`\n\n## Suggested reviewers\n- greg-in-a-box\n- jfox-box\n- bfoxx1906\n\n## Poem\n> I'm a rabbit in a code-filled glade,  \n> I stitched new tabs where old ones played.  \n> Native BoxAI keeps its crown,  \n> Custom panels hop and settle down. 🐇✨\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary>\n\n<details>\n<summary>❌ Failed checks (1 warning)</summary>\n\n|     Check name    | Status     | Explanation                                                                                                                                                                                    | Resolution                                                                                                                                                  |\n| :---------------: | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Description check | ⚠️ Warning | The pull request description contains only an HTML comment template about merge procedures and does not provide actual context about the changes, objectives, rationale, or testing performed. | Replace the template comment with a substantive description explaining the feature, why it was added, how it works, any breaking changes, and testing done. |\n\n</details>\n<details>\n<summary>✅ Passed checks (2 passed)</summary>\n\n|     Check name     | Status   | Explanation                                                                                                                                              |\n| :----------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|     Title check    | ✅ Passed | The title clearly and specifically describes the main feature addition: implementing support for custom sidebar panels in the content sidebar component. |\n| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.                                               |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=box/box-ui-elements&utm_content=4326)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":1}},"response":{"url":"https://api.github.com/repos/box/box-ui-elements/issues/comments/3373043675","status":404,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Wed, 21 Jan 2026 03:09:16 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept-Encoding, Accept, X-Requested-With","x-accepted-github-permissions":"issues=write; pull_requests=write","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-api-version-selected":"2022-11-28","x-github-media-type":"github.v3; format=json","x-github-request-id":"404F:121C63:229D1D2:966DB0F:6970435C","x-ratelimit-limit":"8050","x-ratelimit-remaining":"8002","x-ratelimit-reset":"1768968209","x-ratelimit-resource":"core","x-ratelimit-used":"48","x-xss-protection":"0"},"data":{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#update-an-issue-comment","status":"404"}}}

Copy link
Contributor

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/elements/content-sidebar/SidebarNav.js`:
- Around line 202-213: Fix the Box AI button attributes by using the actual tab
id in the data-target-id (replace the literal `$boxAI` with boxAiTab.id so it
renders like SidebarNavButton-{boxAiTab.id}) and move the spread
{...boxAiTab.navButtonProps} to after the explicit data-* props (data-target-id
and data-resin-target) so consumers can override those attributes; update the
SidebarNavButton usage where hasNativeBoxAISidebar and boxAiTab are used to
apply these changes.
🧹 Nitpick comments (1)
src/elements/content-sidebar/SidebarPanels.js (1)

392-412: Consider removing redundant component check.

Line 392 already guards against !CustomPanelComponent by returning null. The ternary check on line 403 (CustomPanelComponent ? ... : null) is redundant since execution only reaches this point when CustomPanelComponent is truthy.

Based on learnings, the component field is required in the CustomSidebarPanel type, so Flow will catch missing components at compile time.

Suggested simplification
                                 return CustomPanelComponent ? (
-                                        <CustomPanelComponent
+                                return (
+                                    <CustomPanelComponent
                                         elementId={elementId}
                                         file={file}
                                         fileId={fileId}
                                         fileExtension={file.extension}
                                         hasSidebarInitialized={isInitialized}
                                         ref={this.getCustomSidebarRef(customPanelId)}
                                     />
-                                    ) : null;
+                                );

Copy link
Contributor

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/elements/content-sidebar/SidebarPanels.js`:
- Around line 311-333: panelsEligibility currently marks SIDEBAR_VIEW_BOXAI
eligible using hasNativeBoxAISidebar even when showOnlyBoxAINavButton prevents
showing the panel; update the mapping to use the computed
canShowBoxAISidebarPanel (or combine hasNativeBoxAISidebar &&
!showOnlyBoxAINavButton) for SIDEBAR_VIEW_BOXAI so the eligibility reflects the
actual visibility logic used elsewhere and prevents redirects to /boxai when the
panel is hidden; adjust the entry keyed by SIDEBAR_VIEW_BOXAI in
panelsEligibility accordingly.
♻️ Duplicate comments (2)
src/elements/content-sidebar/SidebarNav.js (1)

203-210: Fix custom Box AI data attributes and allow overrides.

Line 206 hardcodes $boxAI, and Line 208 spreads navButtonProps before data-resin-target, which blocks overrides. Align with the custom tab ordering and use the real id.

✅ Suggested fix
-                data-target-id={`SidebarNavButton-$boxAI`}
-                data-testid={`sidebar${boxAiTab.id}`}
-                {...boxAiTab.navButtonProps}
-                data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
+                data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
+                data-target-id={`SidebarNavButton-${boxAiTab.id}`}
+                data-testid={`sidebar${boxAiTab.id}`}
+                {...boxAiTab.navButtonProps}

Based on learnings, keep navButtonProps after explicit data-* attributes to allow overrides.

src/elements/content-sidebar/__tests__/SidebarPanels.test.js (1)

671-686: Use rendered testids for custom panel absence checks.

CustomPanel[id="custom1"] doesn’t exist, so these assertions are ineffective. Use the actual rendered data-testid values.

✅ Suggested fix
-            expect(wrapper.exists('CustomPanel[id="custom1"]')).toBe(false);
-            expect(wrapper.exists('CustomPanel[id="custom2"]')).toBe(false);
+            expect(wrapper.find('div[data-testid="custom1-sidebar"]')).toHaveLength(0);
+            expect(wrapper.find('div[data-testid="custom2-sidebar"]')).toHaveLength(0);

Copy link
Contributor

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/elements/content-sidebar/SidebarPanels.js (1)

313-347: Prevent empty/disabled custom panels from triggering a redirect loop.

hasCustomPanels only checks array length. If all custom panels are disabled/ineligible, the early-return doesn’t fire, and the fallback redirect can’t find an eligible panel, causing redirects to / with nothing renderable. Consider deriving a “has eligible custom panels” flag from customPanelEligibility and using that in the early-return guard.

💡 Suggested fix
-        const hasCustomPanels = customPanels && customPanels.length > 0;
+        const hasCustomPanels = customPanels && customPanels.length > 0;

         const customPanelEligibility = {};
         if (hasCustomPanels) {
             // $FlowFixMe: customPanels is checked for existence in hasCustomPanels
             customPanels.forEach(({ id, path, isDisabled }) => {
                 const isBoxAICustomPanel = id === SIDEBAR_VIEW_BOXAI;
                 const isEligible = isBoxAICustomPanel ? !canShowBoxAISidebarPanel && !isDisabled : !isDisabled;
                 customPanelEligibility[path] = isEligible;
             });
         }
+        const hasEligibleCustomPanels = Object.values(customPanelEligibility).some(Boolean);

         if (
             !isOpen ||
             (!hasActivity &&
                 !hasNativeBoxAISidebar &&
                 !hasDetails &&
                 !hasMetadata &&
                 !hasSkills &&
                 !hasDocGen &&
                 !hasVersions &&
-                !hasCustomPanels)
+                !hasEligibleCustomPanels)
         ) {
             return null;
         }
♻️ Duplicate comments (1)
src/elements/content-sidebar/__tests__/SidebarPanels.test.js (1)

805-808: Use DOM testids instead of a non-existent CustomPanel selector.

CustomPanel[id="custom1"] doesn’t exist in the render tree. Assert against the actual data-testid output.

✅ Suggested fix
-            expect(wrapper.exists('CustomPanel[id="custom1"]')).toBe(false);
-            expect(wrapper.exists('CustomPanel[id="custom2"]')).toBe(false);
+            expect(wrapper.find('div[data-testid="custom1-sidebar"]')).toHaveLength(0);
+            expect(wrapper.find('div[data-testid="custom2-sidebar"]')).toHaveLength(0);
🧹 Nitpick comments (2)
src/elements/content-sidebar/stories/tests/ContentSidebar-visual.stories.tsx (1)

89-125: Strengthen the “default panel” assertion.
Line 121-124 only checks presence; add an aria-selected check to confirm it is actually the default.

♻️ Suggested tweak to the play assertion
-        await waitFor(() => expect(canvas.getByTestId('sidebarcustomPanel')).toBeInTheDocument(), { timeout: 5000 });
+        await waitFor(
+            () => {
+                const customPanelButton = canvas.getByTestId('sidebarcustomPanel');
+                expect(customPanelButton).toBeInTheDocument();
+                expect(customPanelButton).toHaveAttribute('aria-selected', 'true');
+            },
+            { timeout: 5000 },
+        );
src/elements/content-sidebar/SidebarPanels.js (1)

392-413: Remove redundant CustomPanelComponent existence checks.

CustomSidebarPanel.component is required, so the !CustomPanelComponent guard and the ternary render are redundant and can mask contract violations. Simplify to render the component directly and keep the isDisabled guard only.

♻️ Suggested simplification
-                        if (isDisabled || !CustomPanelComponent) {
+                        if (isDisabled) {
                             return null;
                         }

                         return (
                             <Route
                                 exact
                                 key={customPanelId}
                                 path={`/${customPanelPath}`}
                                 render={() => {
                                     this.handlePanelRender(customPanelPath);
-                                    return CustomPanelComponent ? (
-                                        <CustomPanelComponent
-                                            elementId={elementId}
-                                            file={file}
-                                            fileId={fileId}
-                                            fileExtension={file.extension}
-                                            hasSidebarInitialized={isInitialized}
-                                            ref={this.getCustomSidebarRef(customPanelId)}
-                                        />
-                                    ) : null;
+                                    return (
+                                        <CustomPanelComponent
+                                            elementId={elementId}
+                                            file={file}
+                                            fileId={fileId}
+                                            fileExtension={file.extension}
+                                            hasSidebarInitialized={isInitialized}
+                                            ref={this.getCustomSidebarRef(customPanelId)}
+                                        />
+                                    );
                                 }}
                             />
                         );
Based on learnings, prefer relying on the Flow-required `component` contract here.

Copy link
Contributor

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/elements/content-sidebar/SidebarPanels.js (1)

276-347: Prevent redirecting to “/” when no panels are eligible.
If showOnlyBoxAINavButton is true (or all custom panels are disabled) and no other panels are eligible, redirect stays empty and the router navigates to /. Guard the “no eligible panels” case so the sidebar doesn’t bounce to root.

🐛 Suggested fix
-        const showDefaultPanel: boolean = !!(defaultPanel && panelsEligibility[defaultPanel]);
+        const showDefaultPanel: boolean = !!(defaultPanel && panelsEligibility[defaultPanel]);
+        const hasEligiblePanels: boolean = Object.values(panelsEligibility).some(Boolean);

-        if (
-            !isOpen ||
-            (!hasActivity &&
-                !hasNativeBoxAISidebar &&
-                !hasDetails &&
-                !hasMetadata &&
-                !hasSkills &&
-                !hasDocGen &&
-                !hasVersions &&
-                !hasCustomPanels)
-        ) {
+        if (!isOpen || (!hasEligiblePanels && !hasVersions)) {
             return null;
         }
...
-                            const firstEligiblePanel = panelOrder.find(panel => panelsEligibility[panel]);
-                            if (firstEligiblePanel) {
-                                redirect = firstEligiblePanel;
-                            }
+                            const firstEligiblePanel = panelOrder.find(panel => panelsEligibility[panel]);
+                            if (firstEligiblePanel) {
+                                redirect = firstEligiblePanel;
+                            } else {
+                                return null;
+                            }

Also applies to: 579-589

🧹 Nitpick comments (1)
src/elements/content-sidebar/SidebarPanels.js (1)

377-414: Remove redundant CustomPanelComponent existence checks.
component is required by the CustomSidebarPanel type, so the runtime null checks add noise without real protection.

♻️ Proposed cleanup
-                        if (isDisabled || !CustomPanelComponent) {
+                        if (isDisabled) {
                             return null;
                         }
...
-                                    return CustomPanelComponent ? (
-                                        <CustomPanelComponent
-                                            elementId={elementId}
-                                            file={file}
-                                            fileId={fileId}
-                                            fileExtension={file.extension}
-                                            hasSidebarInitialized={isInitialized}
-                                            ref={this.getCustomSidebarRef(customPanelId)}
-                                        />
-                                    ) : null;
+                                    return (
+                                        <CustomPanelComponent
+                                            elementId={elementId}
+                                            file={file}
+                                            fileId={fileId}
+                                            fileExtension={file.extension}
+                                            hasSidebarInitialized={isInitialized}
+                                            ref={this.getCustomSidebarRef(customPanelId)}
+                                        />
+                                    );

Based on learnings, the component field is required by Flow and doesn’t need runtime guards.

Copy link
Contributor

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/elements/content-sidebar/SidebarPanels.js`:
- Around line 339-348: The empty-panels guard currently checks
hasNativeBoxAISidebar which remains true when showOnlyBoxAINavButton is set,
causing a false-positive "has panel" condition; update the guard to use
canShowBoxAISidebarPanel instead so the logic respects the computed visibility
(replace hasNativeBoxAISidebar with canShowBoxAISidebarPanel in the conditional
that decides whether to short-circuit when no panels are available, e.g., in the
block containing isOpen and the list of
hasActivity/hasDetails/.../hasCustomPanels).
♻️ Duplicate comments (1)
src/elements/content-sidebar/SidebarNav.js (1)

230-238: Fix Box AI custom tab data attributes and override order.

data-target-id is hardcoded and navButtonProps is spread before data-resin-target, preventing consumers from overriding analytics attributes.

✅ Proposed fix
-                data-target-id={`SidebarNavButton-boxAI`}
-                data-testid={`sidebar${boxAiPanel.id}`}
-                {...boxAiPanel.navButtonProps}
-                data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
+                data-resin-target={SIDEBAR_NAV_TARGETS.BOXAI}
+                data-target-id={`SidebarNavButton-${boxAiPanel.id}`}
+                data-testid={`sidebar${boxAiPanel.id}`}
+                {...boxAiPanel.navButtonProps}

Based on learnings, keep navButtonProps after explicit data-* props to allow overrides.

🧹 Nitpick comments (1)
src/elements/content-sidebar/SidebarPanels.js (1)

395-397: Drop redundant CustomPanelComponent null check.

component is required in CustomSidebarPanel, so this check can be removed.

Based on learnings, rely on the Flow type contract instead of runtime checks.

jpan-box
jpan-box previously approved these changes Jan 22, 2026
Copy link
Contributor

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/elements/content-sidebar/SidebarPanels.js (1)

315-349: Treat all‑disabled custom panels as “no panels” in the guard.

hasCustomPanels is length‑based, so if all custom panels are disabled and no native panels are eligible, the guard won’t short‑circuit and the fallback redirect can loop to / with no eligible panel. Consider tracking eligibility and using that in the guard.

💡 Suggested adjustment
-        const customPanelEligibility = {};
+        const customPanelEligibility = {};
+        let hasEligibleCustomPanels = false;
         if (hasCustomPanels) {
             // $FlowFixMe: customSidebarPanels is checked for existence in hasCustomPanels
             customSidebarPanels.forEach(({ id, path, isDisabled }) => {
                 const isBoxAICustomPanel = id === SIDEBAR_VIEW_BOXAI;
                 const isEligible = isBoxAICustomPanel ? !canShowBoxAISidebarPanel && !isDisabled : !isDisabled;
                 customPanelEligibility[path] = isEligible;
+                if (isEligible) {
+                    hasEligibleCustomPanels = true;
+                }
             });
         }
...
-                !hasVersions &&
-                !hasCustomPanels)
+                !hasVersions &&
+                !hasEligibleCustomPanels)
         ) {
             return null;
         }
♻️ Duplicate comments (1)
src/elements/content-sidebar/__tests__/SidebarPanels.test.js (1)

814-822: Use DOM testids instead of the non‑existent CustomPanel selector.

The test is asserting against a component that doesn’t exist in the render output, so it isn’t actually validating the DOM.

✅ Fix
-            expect(wrapper.exists('CustomPanel[id="custom1"]')).toBe(false);
-            expect(wrapper.exists('CustomPanel[id="custom2"]')).toBe(false);
+            expect(wrapper.find('div[data-testid="custom1-sidebar"]')).toHaveLength(0);
+            expect(wrapper.find('div[data-testid="custom2-sidebar"]')).toHaveLength(0);

Copy link
Contributor

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/elements/content-sidebar/__tests__/SidebarPanels.test.js`:
- Around line 838-842: The test uses invalid component selectors
'CustomPanel[id="custom1"]' and 'CustomPanel[id="custom2"]' which don't
correspond to rendered elements; update the assertions to check the actual
rendered testids instead (e.g., replace
wrapper.exists('CustomPanel[id="custom1"]') /
wrapper.exists('CustomPanel[id="custom2"]') with
wrapper.exists('[data-testid="custom1"]') /
wrapper.exists('[data-testid="custom2"]') or the project's equivalent testid
selectors) so the assertions target the real DOM nodes alongside the existing
wrapper.exists('BoxAISidebar') check.

@fpan225 fpan225 force-pushed the custom-sidebar-panels branch from c3e35b5 to 08cdea1 Compare January 27, 2026 21:31
kduncanhsu
kduncanhsu previously approved these changes Jan 28, 2026
Copy link
Contributor

@kduncanhsu kduncanhsu left a comment

Choose a reason for hiding this comment

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

lgtm

@fpan225 fpan225 force-pushed the custom-sidebar-panels branch from e11a2b7 to ed1dae4 Compare February 5, 2026 19:10
@fpan225 fpan225 assigned CLAassistant and unassigned CLAassistant Feb 5, 2026
@mergify mergify bot added the queued label Feb 6, 2026
@mergify mergify bot merged commit 7148457 into master Feb 6, 2026
12 checks passed
@mergify mergify bot deleted the custom-sidebar-panels branch February 6, 2026 16:33
@mergify
Copy link
Contributor

mergify bot commented Feb 6, 2026

Merge Queue Status

✅ The pull request has been merged at 5a67dd5

This pull request spent 7 seconds in the queue, with no time running CI.
The checks were run in-place.

Required conditions to merge

@mergify mergify bot removed the queued label Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants