feat(settings): unify panels under one consistent SettingsPanel template#4037
Conversation
Introduce a single SettingsPanel template that bakes in a consistent title (derived from the route registry), back button, full-width body spacing, and the sibling sub-nav, so every Settings page shares one header structure. Migrate all panels (and WorkflowsTab) onto it, move the sub-nav into the panel header, and add an optional title to the PanelHeader/PanelScaffold/PanelPage primitives.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
💤 Files with no reviewable changes (2)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughA new ChangesSettingsPanel layout rollout
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4bf12c009d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| {/* Refreshing indicator - only when loading and has existing data */} | ||
| {isLoadingMembers && members.length > 0 && ( | ||
| <InlineLoadingStatus label={t('team.refreshingMembers')} /> | ||
| <SettingsPanel description={t('pages.settings.account.teamDesc')}> |
There was a problem hiding this comment.
When this panel is reached via /settings/team/members or /settings/team/manage/:teamId/members, useSettingsNavigation() resolves the current route as team-members, but that id is not present in settingsRouteRegistry, so SettingsPanel falls back to t('nav.settings'). Because this call omits an explicit title, the new header shows a generic “Settings” title for the members page; pass title={t('team.members')} here (as the invites panel now does) or register the route id.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1291d15 — passing title={t('team.members')} (the team-members route id isn't in the registry, so the fallback was showing "Settings"). Matches the invites panel.
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
app/src/components/layout/PanelPage.tsx (1)
123-141: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRender
headerExtrain the tabbed header path too.The single-body branch forwards
headerExtra, but the tabbed branch always renders onlyChipTabs. That means anySettingsPanelusing tabs loses the injectedSettingsSubNav, which breaks the new “sub-nav lives in the header” contract for tabbed settings pages. Based on the SettingsPanel rollout contract in the PR summary and review stack context.🤖 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 `@app/src/components/layout/PanelPage.tsx` around lines 123 - 141, The tabbed header path in PanelPage is not rendering headerExtra, so SettingsPanel sub-navigation gets dropped when tabs are present. Update the PanelPage header render branch that uses PanelHeader and ChipTabs to also forward and render headerExtra alongside the tabs, matching the single-body branch behavior and preserving the header sub-nav contract for tabbed settings pages.app/src/components/settings/panels/PersonalityPanel.tsx (1)
33-51: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep SettingsSubNav in tabbed SettingsPanel headers.
This tabbed
SettingsPaneltakes the upstreamtabs && tabs.length > 0branch, but that branch does not passheaderExtra={subNav}. Personality & Face will therefore lose the sibling sub-nav row despite the new layout contract. AddheaderExtra={subNav}to the tabbedPanelPagebranch inSettingsPanel.🤖 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 `@app/src/components/settings/panels/PersonalityPanel.tsx` around lines 33 - 51, The tabbed SettingsPanel path is missing the sibling sub-nav row because the `PanelPage` branch used when `tabs && tabs.length > 0` does not pass `headerExtra={subNav}`. Update `SettingsPanel` so the tabbed branch also forwards `subNav` through `headerExtra`, preserving the SettingsSubNav in the Personality/Face header layout while keeping the existing tab rendering logic in `PersonalityPanel` and related callers intact.app/src/components/settings/panels/NotificationsTabbedPanel.tsx (1)
34-51: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTabbed notifications lose the settings sub-nav in the new wrapper.
This consumer now goes through
SettingsPanel’stabsbranch, but the providedSettingsPanelimplementation only injects the sibling sub-nav (headerExtra={subNav}) in the non-tab path. That means/settings/notificationsno longer matches the new “title + sibling pills in the header” contract.The fix likely belongs in
app/src/components/settings/layout/SettingsPanel.tsxso the tabbed branch forwards the same header-extra region too.🤖 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 `@app/src/components/settings/panels/NotificationsTabbedPanel.tsx` around lines 34 - 51, The tabbed notifications view is missing the sibling sub-nav because the SettingsPanel tabs branch does not forward the header extra region. Update SettingsPanel so the tabbed path in SettingsPanel still renders the same header-extra/sub-nav area as the non-tab path, preserving the title + sibling pills contract for NotificationsTabbedPanel and other tabbed consumers.app/src/components/settings/panels/IntegrationsPanel.tsx (1)
43-61: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winTabbed
SettingsPanelcurrently drops the sibling sub-nav.This route now renders through
SettingsPanel’s tabbed branch, but the suppliedapp/src/components/settings/layout/SettingsPanel.tsxsnippet only passesheaderExtra={subNav}in the non-tabbed branch. The result is that/settings/integrationsloses the sibling settings pills, which breaks the new “sub-nav in every panel header” contract.🤖 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 `@app/src/components/settings/panels/IntegrationsPanel.tsx` around lines 43 - 61, The tabbed IntegrationsPanel path is not preserving the sibling settings pills because SettingsPanel only forwards headerExtra via subNav in the non-tabbed branch. Update SettingsPanel so the headerExtra/subNav content is rendered for both the tabbed and non-tabbed layouts, and verify the IntegrationsPanel route still shows the sibling pills alongside the tabs. Use the SettingsPanel component and its tabbed rendering branch to locate the fix.app/src/components/settings/panels/UsagePanel.tsx (1)
37-55: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTabbed
SettingsPaneldrops the sibling sub-nav here.This route now goes through the tabbed branch of
SettingsPanel, but the suppliedSettingsPanelimplementation only injectsSettingsSubNavin the non-tab branch. That means/settings/usageloses the sibling pill row the rollout is trying to standardize across settings pages.🤖 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 `@app/src/components/settings/panels/UsagePanel.tsx` around lines 37 - 55, The tabbed SettingsPanel usage in UsagePanel is bypassing the sibling sub-nav, so the route no longer shows the SettingsSubNav pill row. Update the SettingsPanel tabbed branch to also render the sibling sub-nav when available, or adjust the UsagePanel composition so the sub-nav is preserved alongside the tabs; use SettingsPanel, SettingsSubNav, and UsagePanel as the main anchors while making the change.
🧹 Nitpick comments (3)
app/src/components/settings/layout/SettingsPanel.test.tsx (1)
66-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTab-switch test doesn’t verify the switch.
The test clicks the second tab but never updates
value, so it only verifiesonChange(controlled behavior), not content swap. Either rename the test or rerender withvalue="two"and assert bodies swap.Proposed test adjustment
- it('renders chip tabs and swaps to the active tab content', () => { + it('calls onChange and renders new active content after value update', () => { const onChange = vi.fn(); - const { getByText, queryByText, getByTestId } = renderWithProviders( + const tabs = [ + { id: 'one', label: 'One', content: <p>first body</p> }, + { id: 'two', label: 'Two', content: <p>second body</p> }, + ] as const; + const { getByText, queryByText, getByTestId, rerender } = renderWithProviders( <SettingsPanel value="one" onChange={onChange} tabsTestIdPrefix="demo-tab" - tabs={[ - { id: 'one', label: 'One', content: <p>first body</p> }, - { id: 'two', label: 'Two', content: <p>second body</p> }, - ]} + tabs={tabs as any} /> ); @@ fireEvent.click(getByTestId('demo-tab-two')); expect(onChange).toHaveBeenCalledWith('two'); + rerender( + <SettingsPanel + value="two" + onChange={onChange} + tabsTestIdPrefix="demo-tab" + tabs={tabs as any} + /> + ); + expect(getByText('second body')).toBeInTheDocument(); + expect(queryByText('first body')).not.toBeInTheDocument(); });🤖 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 `@app/src/components/settings/layout/SettingsPanel.test.tsx` around lines 66 - 85, The SettingsPanel test only checks the controlled onChange callback after clicking the second tab, so it does not verify the content actually switches. Update the test in SettingsPanel.test.tsx to either reflect controlled behavior in the name or rerender SettingsPanel with value set to "two" after the click, then assert the active content changes from "first body" to "second body". Use the existing SettingsPanel, renderWithProviders, and getByTestId setup to keep the test focused on tab switching.app/src/components/settings/panels/AnalysisViewsPanel.tsx (1)
49-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse
SettingsPanel's tab slot here.This keeps the chip row in body content instead of the standardized header region that
SettingsPanelalready provides. Passingviewsthroughtabs,value, andonChangewould preserve the intended title → description → tabs → body order for settings pages.🤖 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 `@app/src/components/settings/panels/AnalysisViewsPanel.tsx` around lines 49 - 65, The tab row is rendered inside the body of SettingsPanel instead of using its built-in tab slot. Update AnalysisViewsPanel to pass the existing views data through SettingsPanel’s tabs, value, and onChange props, and remove the inline PillTabBar so the header keeps the standard title, description, tabs, body order. Use the SettingsPanel and views symbols to locate the affected markup and preserve the current tab switching behavior with activeView/setActiveView.app/src/components/settings/panels/ApprovalHistoryPanel.tsx (1)
89-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove the subtitle and refresh button into the panel header.
This migration still renders both inside the first section, so the page misses the standardized
SettingsPaneldescription/action layout. Wiret('settings.approvalHistory.subtitle')throughdescriptionand the refresh control throughaction.🤖 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 `@app/src/components/settings/panels/ApprovalHistoryPanel.tsx` around lines 89 - 104, The ApprovalHistoryPanel still renders the subtitle and refresh button inside the first SettingsSection instead of using the SettingsPanel header layout. Update ApprovalHistoryPanel to pass t('settings.approvalHistory.subtitle') into SettingsPanel’s description prop and move the refresh Button into its action prop, keeping handleRefresh and isLoading behavior unchanged.
🤖 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 `@app/src/components/layout/PanelScaffold.tsx`:
- Around line 73-84: The scaffold still mounts PanelHeader when only leading is
provided, which can leave an empty fixed header gap above the body on wide
viewports. Update PanelScaffold’s header gating logic so the header is only
rendered when there is visible header content, using the existing
hasVisibleHeader condition (or equivalent) rather than hasHeader, and keep the
border decision separate from whether the header mounts.
In `@app/src/components/settings/layout/SettingsPanel.tsx`:
- Around line 77-91: The tabbed render path in SettingsPanel omits headerExtra,
so the in-panel header layout is inconsistent with the non-tab branch. Update
the PanelPage<T> usage in SettingsPanel to pass headerExtra through in the tabs
&& tabs.length > 0 branch as well, keeping the header order consistent with
SettingsSubNav and the existing header contract.
In `@app/src/components/settings/panels/AgentActivityPanel.tsx`:
- Around line 109-113: The panel is rendering the same activity-level
description twice: once through SettingsPanel and again inside
AgentActivityPanel. Remove the extra <p> block in AgentActivityPanel so the
intro text is only provided via SettingsPanel, keeping the panel content from
duplicating the copy and spacing.
In `@app/src/components/settings/panels/AppearancePanel.tsx`:
- Around line 157-169: In AppearancePanel’s radio button styling, the base class
currently applies the selected dark-mode background to every option, so
unselected rows look active. Move the dark background class into the selected
branch of the className conditional for the option buttons rendered in this
picker, and apply the same fix to the other matching picker block referenced by
the review.
In `@app/src/components/settings/panels/ComposioTriagePanel.tsx`:
- Around line 93-118: The intro copy and the section description in
ComposioTriagePanel are built from multiple translation calls, which prevents
translators from controlling sentence order and placement of the env-var token.
Update the text to use single translation keys for each full sentence, then
inject OPENHUMAN_TRIGGER_TRIAGE_DISABLED using the existing repo pattern with
t(...).replace(...), keeping the logic localized around ComposioTriagePanel and
its SettingsPanel/SettingsSection content.
In `@app/src/components/settings/panels/CronJobsPanel.tsx`:
- Around line 188-230: The cron jobs panel is wrapping CoreJobList in an extra
SettingsSection, which duplicates the section chrome and heading. Update
CronJobsPanel so the cron jobs content uses only the existing CoreJobList
section, keeping the surrounding SettingsPanel/SettingsStatusLine and buttons
but removing the redundant outer SettingsSection around CoreJobList.
In `@app/src/components/settings/panels/NotificationsPanel.tsx`:
- Around line 122-127: The category switch aria-label in NotificationsPanel is
hardcoded English and bypasses localization. Update the SettingsSwitch
aria-label in NotificationsPanel to use the existing useT() translation hook
with a new key for the full “Toggle {category} notifications” label, and add
that key to en.ts plus all other locale files so screen readers announce the
control in the active language.
In `@app/src/components/settings/panels/ProfileEditorPage.tsx`:
- Around line 182-189: In ProfileEditorPage’s notFound branch, remove the extra
back button so the header-level back control from SettingsPanel remains the only
back affordance. Update the conditional render around the notFound message to
keep just the notice and drop the Button that calls backToList.
In `@app/src/components/settings/panels/SearchPanel.tsx`:
- Around line 461-464: The standalone SearchPanel is still rendering
SettingsPanel chrome even though it is used outside the settings route tree, so
the title and back behavior become route-dependent in the wrong place. Update
SearchPanel and its non-embedded caller in Skills.tsx so the SettingsPanel
wrapper is only used for the embedded/settings-route case, or move that chrome
to the host page instead. Use the SearchPanel component’s embedded/non-embedded
branch and the SettingsPanel wrapper as the main points to adjust.
In `@app/src/components/settings/panels/VoicePanel.tsx`:
- Around line 952-957: The fallback `'Test failed'` string in `VoicePanel`
bypasses `useT()` and should be localized like the rest of the panel copy.
Update the error-handling paths in the affected voice test flows (including the
`catch` blocks around the key test result setters) to use i18n keys from
`app/src/lib/i18n/I18nContext` instead of hardcoded English text. Add the new
translation key(s) to `en.ts` and every locale file so the `setKeyTestResult`
and related error messages stay locale-parity compliant.
---
Outside diff comments:
In `@app/src/components/layout/PanelPage.tsx`:
- Around line 123-141: The tabbed header path in PanelPage is not rendering
headerExtra, so SettingsPanel sub-navigation gets dropped when tabs are present.
Update the PanelPage header render branch that uses PanelHeader and ChipTabs to
also forward and render headerExtra alongside the tabs, matching the single-body
branch behavior and preserving the header sub-nav contract for tabbed settings
pages.
In `@app/src/components/settings/panels/IntegrationsPanel.tsx`:
- Around line 43-61: The tabbed IntegrationsPanel path is not preserving the
sibling settings pills because SettingsPanel only forwards headerExtra via
subNav in the non-tabbed branch. Update SettingsPanel so the headerExtra/subNav
content is rendered for both the tabbed and non-tabbed layouts, and verify the
IntegrationsPanel route still shows the sibling pills alongside the tabs. Use
the SettingsPanel component and its tabbed rendering branch to locate the fix.
In `@app/src/components/settings/panels/NotificationsTabbedPanel.tsx`:
- Around line 34-51: The tabbed notifications view is missing the sibling
sub-nav because the SettingsPanel tabs branch does not forward the header extra
region. Update SettingsPanel so the tabbed path in SettingsPanel still renders
the same header-extra/sub-nav area as the non-tab path, preserving the title +
sibling pills contract for NotificationsTabbedPanel and other tabbed consumers.
In `@app/src/components/settings/panels/PersonalityPanel.tsx`:
- Around line 33-51: The tabbed SettingsPanel path is missing the sibling
sub-nav row because the `PanelPage` branch used when `tabs && tabs.length > 0`
does not pass `headerExtra={subNav}`. Update `SettingsPanel` so the tabbed
branch also forwards `subNav` through `headerExtra`, preserving the
SettingsSubNav in the Personality/Face header layout while keeping the existing
tab rendering logic in `PersonalityPanel` and related callers intact.
In `@app/src/components/settings/panels/UsagePanel.tsx`:
- Around line 37-55: The tabbed SettingsPanel usage in UsagePanel is bypassing
the sibling sub-nav, so the route no longer shows the SettingsSubNav pill row.
Update the SettingsPanel tabbed branch to also render the sibling sub-nav when
available, or adjust the UsagePanel composition so the sub-nav is preserved
alongside the tabs; use SettingsPanel, SettingsSubNav, and UsagePanel as the
main anchors while making the change.
---
Nitpick comments:
In `@app/src/components/settings/layout/SettingsPanel.test.tsx`:
- Around line 66-85: The SettingsPanel test only checks the controlled onChange
callback after clicking the second tab, so it does not verify the content
actually switches. Update the test in SettingsPanel.test.tsx to either reflect
controlled behavior in the name or rerender SettingsPanel with value set to
"two" after the click, then assert the active content changes from "first body"
to "second body". Use the existing SettingsPanel, renderWithProviders, and
getByTestId setup to keep the test focused on tab switching.
In `@app/src/components/settings/panels/AnalysisViewsPanel.tsx`:
- Around line 49-65: The tab row is rendered inside the body of SettingsPanel
instead of using its built-in tab slot. Update AnalysisViewsPanel to pass the
existing views data through SettingsPanel’s tabs, value, and onChange props, and
remove the inline PillTabBar so the header keeps the standard title,
description, tabs, body order. Use the SettingsPanel and views symbols to locate
the affected markup and preserve the current tab switching behavior with
activeView/setActiveView.
In `@app/src/components/settings/panels/ApprovalHistoryPanel.tsx`:
- Around line 89-104: The ApprovalHistoryPanel still renders the subtitle and
refresh button inside the first SettingsSection instead of using the
SettingsPanel header layout. Update ApprovalHistoryPanel to pass
t('settings.approvalHistory.subtitle') into SettingsPanel’s description prop and
move the refresh Button into its action prop, keeping handleRefresh and
isLoading behavior unchanged.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8f551f51-a75e-4d89-9761-ae32f51d6c9f
📒 Files selected for processing (77)
app/src/components/dashboard/CostDashboardPanel.tsxapp/src/components/intelligence/WorkflowsTab.tsxapp/src/components/layout/PanelHeader.tsxapp/src/components/layout/PanelPage.tsxapp/src/components/layout/PanelScaffold.tsxapp/src/components/settings/components/SettingsHeader.tsxapp/src/components/settings/layout/SettingsLayout.tsxapp/src/components/settings/layout/SettingsPanel.test.tsxapp/src/components/settings/layout/SettingsPanel.tsxapp/src/components/settings/layout/SettingsSubNav.tsxapp/src/components/settings/modal/SettingsModalFrame.test.tsxapp/src/components/settings/modal/SettingsModalLayout.tsxapp/src/components/settings/panels/AIPanel.tsxapp/src/components/settings/panels/AboutPanel.tsxapp/src/components/settings/panels/AccountPanel.tsxapp/src/components/settings/panels/AgentAccessPanel.tsxapp/src/components/settings/panels/AgentActivityPanel.tsxapp/src/components/settings/panels/AgentBoxPanel.tsxapp/src/components/settings/panels/AgentChatPanel.tsxapp/src/components/settings/panels/AgentEditorPage.tsxapp/src/components/settings/panels/AgentsPanel.tsxapp/src/components/settings/panels/AnalysisViewsPanel.tsxapp/src/components/settings/panels/AppearancePanel.tsxapp/src/components/settings/panels/ApprovalHistoryPanel.tsxapp/src/components/settings/panels/AutocompleteDebugPanel.tsxapp/src/components/settings/panels/AutocompletePanel.tsxapp/src/components/settings/panels/BillingPanel.tsxapp/src/components/settings/panels/CompanionPanel.tsxapp/src/components/settings/panels/ComposioPanel.tsxapp/src/components/settings/panels/ComposioTriagePanel.tsxapp/src/components/settings/panels/CronJobsPanel.tsxapp/src/components/settings/panels/DesktopAgentPanel.tsxapp/src/components/settings/panels/DevWorkflowPanel.tsxapp/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/DevicesPanel.tsxapp/src/components/settings/panels/EmbeddingsPanel.tsxapp/src/components/settings/panels/EventLogPanel.tsxapp/src/components/settings/panels/IntegrationsPanel.tsxapp/src/components/settings/panels/LocalModelDebugPanel.tsxapp/src/components/settings/panels/MascotPanel.tsxapp/src/components/settings/panels/McpServerPanel.tsxapp/src/components/settings/panels/MeetingSettingsPanel.tsxapp/src/components/settings/panels/MemoryDataPanel.tsxapp/src/components/settings/panels/MemoryDebugPanel.tsxapp/src/components/settings/panels/MemorySyncPanel.tsxapp/src/components/settings/panels/MigrationPanel.tsxapp/src/components/settings/panels/ModelHealthPanel.tsxapp/src/components/settings/panels/NotificationRoutingPanel.tsxapp/src/components/settings/panels/NotificationsPanel.tsxapp/src/components/settings/panels/NotificationsTabbedPanel.tsxapp/src/components/settings/panels/PermissionsPanel.tsxapp/src/components/settings/panels/PersonaPanel.tsxapp/src/components/settings/panels/PersonalityPanel.tsxapp/src/components/settings/panels/PrivacyPanel.tsxapp/src/components/settings/panels/ProfileEditorPage.tsxapp/src/components/settings/panels/ProfilesPanel.tsxapp/src/components/settings/panels/RecoveryPhrasePanel.tsxapp/src/components/settings/panels/SandboxSettingsPanel.tsxapp/src/components/settings/panels/ScreenAwarenessDebugPanel.tsxapp/src/components/settings/panels/ScreenIntelligencePanel.tsxapp/src/components/settings/panels/SearchPanel.tsxapp/src/components/settings/panels/SecurityPanel.tsxapp/src/components/settings/panels/TaskSourcesPanel.tsxapp/src/components/settings/panels/TasksPanel.tsxapp/src/components/settings/panels/TeamInvitesPanel.tsxapp/src/components/settings/panels/TeamManagementPanel.tsxapp/src/components/settings/panels/TeamMembersPanel.tsxapp/src/components/settings/panels/TeamPanel.tsxapp/src/components/settings/panels/ToolPolicyDiagnosticsPanel.tsxapp/src/components/settings/panels/ToolsPanel.tsxapp/src/components/settings/panels/UsagePanel.tsxapp/src/components/settings/panels/VoiceDebugPanel.tsxapp/src/components/settings/panels/VoicePanel.tsxapp/src/components/settings/panels/WalletBalancesPanel.tsxapp/src/components/settings/panels/WebhooksDebugPanel.tsxapp/src/components/settings/panels/WorkflowRunnerPanel.tsxapp/src/components/settings/settingsRouteElements.tsx
…ate gaps - Revert AIPanel/VoicePanel/EmbeddingsPanel/SearchPanel/ComposioPanel/ MemoryDataPanel/MemoryDebugPanel/AnalysisViewsPanel: these render on the Connections/Brain pages (their /settings routes redirect), so they must not use the settings-route-derived SettingsPanel chrome. - SettingsPanel: pass headerExtra (sub-nav) in the tabbed branch too, and forward headerExtra through PanelPage's tabbed header. - TeamMembersPanel: pass explicit title (route id not in registry). - AgentActivityPanel: drop description duplicated by the template. - ProfileEditorPage: drop duplicate back button in the not-found state. - AppearancePanel: stop tinting unselected rows in dark mode.
Summary
SettingsPaneltemplate (app/src/components/settings/layout/SettingsPanel.tsx) that every Settings page renders through — it bakes in a consistent title (auto-derived from the route registry'stitleKey), the back button, full-width body spacing (p-4 space-y-5), and the sibling sub-nav, so the header is identical everywhere: title + action → description → tabs → body.WorkflowsTaband the embeddedCostDashboardPanel) onto the template; strip per-panelz-10/contentClassName/inner padding wrappers andmax-w-*width limiters so content is uniformly full-width.titleprop to the sharedPanelHeader/PanelScaffold/PanelPageprimitives (backwards-compatible — generic panels stay title-less).SettingsHeader(kept only for the two non-settings page shells,Webhooks/Intelligence, that live outside the/settingsroute tree).Problem
The Settings panels were visually inconsistent: drifting spacing (
p-4vspx-4 py-3,space-y-4vsspace-y-5), missing/mis-ordered titles, action buttons sometimes in the header and sometimes buried in the body, one panel hand-rolling a deprecated header, and per-panelmax-w-*making content look narrower on some tabs. The cross-panel sub-nav rendered above the panel while in-panel tabs rendered inside it, producing the "title above tabs / tabs above title" mismatch. Switching tabs appeared to resize the modal.Solution
One opinionated template removes the optional knobs that caused drift and derives the title from the existing route registry (so no new i18n keys). The desktop modal card is already fixed-size (
h-[80vh] max-w-5xl); unifying the header structure + full-width content removes the remaining per-tab visual jump. ASettingsModalFrametest asserts the card's size classes are content-independent.Submission Checklist
SettingsPanel.test.tsx(title derivation, override, action, tabs) and a content-independent-size test inSettingsModalFrame.test.tsxdiff-coveris the source of truthN/A: behaviour-preserving UI refactor; no feature rows added/removed/renamed## Related—N/A: no feature rows affectedN/A: presentation-only refactor, no release-cut logicCloses #NNN—N/A: ad-hoc request, no tracking issueImpact
WorkflowsTab(Activity/Intelligence) are unchanged; only the settingsautomationsroute gets the panel chrome.Related
Webhooks,Intelligence) off the deprecatedSettingsHeaderif/when they move under/settings.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
feat/settings-consistent-panel-template4bf12c009de720d765115f64aa44b670b1829e03Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckvitest run src/components/settings src/components/intelligence/__tests__/WorkflowsTab.test.tsx— 97 files / 1167 tests passN/A— no Rust changesN/A— no Tauri changesValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
WorkflowsTabpage-tab usages unchanged (opt-inasSettingsPanelonly on the settings route); deprecatedSettingsHeaderretained for non-settings pagesDuplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Bug Fixes