Skip to content

feat: Collapsible sections — authoring UX + DashboardContainer abstraction#1926

Merged
kodiakhq[bot] merged 10 commits into
mainfrom
feat/collapsible-sections-authoring-ux
Mar 20, 2026
Merged

feat: Collapsible sections — authoring UX + DashboardContainer abstraction#1926
kodiakhq[bot] merged 10 commits into
mainfrom
feat/collapsible-sections-authoring-ux

Conversation

@alex-fedotyev
Copy link
Copy Markdown
Contributor

@alex-fedotyev alex-fedotyev commented Mar 17, 2026

Summary

Adds the authoring experience for dashboard sections (create, rename, delete, manage tiles) and introduces a polymorphic DashboardContainer abstraction that future-proofs the schema for tabs and groups.

Builds on #1900 (core collapsible sections mechanics). Closes #1897.

Schema: DashboardSectionDashboardContainer

  • Renamed DashboardSectionSchemaDashboardContainerSchema with a new type field ('section' for now, extensible to 'group' / 'tab' later)
  • sectionIdcontainerId on tiles
  • sectionscontainers on dashboards
  • Updated across all packages: common-utils types, API Mongoose model, app types, import/export utils

Authoring UX

Action How
Create section Dashboard ... overflow menu → "Add Section"
Rename section Click the title text directly (Kibana-style inline editing)
Delete section Hover section header → ... → Delete Section (tiles become ungrouped, not deleted)
Collapse/expand Click section header chevron
Toggle default state Hover header → ... → Collapse/Expand by Default
Add tile to section Hover section header → + button opens tile editor pre-assigned to that section
Move tile to section Hover tile → grid icon → pick target section from dropdown
Move tile out Same dropdown → "(Ungrouped)"

Mar-18-2026 16-37-58

UX polish (informed by best practices research)

  • Click-to-rename — click section title text to edit inline (no menu navigation needed)
  • Hover-only controls... menu and + button only appear on section header hover, keeping view mode clean
  • "Add Section" demoted — moved from equal-sized button to dashboard overflow menu (section creation is less frequent than tile creation)
  • "Move to Section" reordered — placed before delete button for discoverability, uses IconLayoutList instead of IconFolders

What's NOT in this PR (follow-up work)

  • Drag tiles between sections — needs react-dnd custom drag layer; data model already supports it (containerId update)
  • Reorder sections — needs sortable list library; data model supports it (array order)
  • Tabs / Groups — new container types; just add to the type enum and build UIs

Test plan

  • 30 unit tests pass (16 existing schema/grouping + 14 new authoring operations)
  • All 110 dashboard tests pass unchanged
  • ESLint clean
  • No TypeScript errors in changed files
  • Backward compatible — dashboards without containers render exactly as before

🤖 Generated with Claude Code

Add section management to the dashboard: create, rename, delete sections,
toggle default collapsed state, and move tiles between sections via a
menu on the tile hover toolbar.

Closes #1897
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Mar 20, 2026 4:03pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 17, 2026

⚠️ No Changeset found

Latest commit: 8c37ce0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 17, 2026

PR Review

  • Breaking DB change without migration → The sectionscontainers and sectionIdcontainerId rename in the Mongoose model and Zod schema will silently drop all existing section data for current users. Existing MongoDB documents have sections field; the new model only reads containers. Add a DB migration (or a read-fallback in the API layer) that moves sectionscontainers and adds type: 'section' to each entry.

  • Dashboard import/export backward compatibility brokenconvertToDashboardTemplate and convertToDashboardDocument now only process containers, so importing a previously-exported dashboard JSON (which has sections) will silently drop all section data. Add a backward-compat read shim: input.containers ?? input.sections.

  • ⚠️ deleteSection test helper doesn't test layout preservation → The handleDeleteSection in DBDashboardPage.tsx includes a maxUngroupedY offset calculation to prevent tile collisions. The test deleteSection helper omits this entirely. The actual layout-preservation logic is untested.

  • ⚠️ New required type field on DashboardContainerSchema → Any existing stored dashboard data that gets re-validated through this schema (e.g. in the API response path) will fail Zod parsing because the type field is required but absent in stored documents. This compounds the migration issue above.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 17, 2026

E2E Test Results

All tests passed • 90 passed • 3 skipped • 991s

Status Count
✅ Passed 90
❌ Failed 0
⚠️ Flaky 3
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

Schema changes:
- DashboardSection → DashboardContainer with `type` field (extensible
  to 'group'/'tab' later, only 'section' for now)
- sectionId → containerId on tiles
- sections → containers on dashboard

UX improvements:
- Click section title to rename directly (Kibana pattern)
- Edit controls (... menu) only visible on hover
- "Add Section" moved to dashboard overflow menu (not equal to Add Tile)
- "Move to Section" icon reordered before delete for discoverability
- IconLayoutList replaces IconFolders for clearer visual meaning
- "+" button on section header to add tile directly into that section
@alex-fedotyev alex-fedotyev changed the title feat: Collapsible sections - authoring UX feat: Collapsible sections — authoring UX + DashboardContainer abstraction Mar 17, 2026
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

@claude - code review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 17, 2026

Claude finished @alex-fedotyev's task in 2m 17s —— View job


Code Review

  • Gather context and understand request
  • Read changed files
  • Review API model changes
  • Review common-utils type changes
  • Review frontend components
  • Review tests

Overall this is well-structured work. The schema abstraction is clean, the UX is thoughtful, and the tests are thorough. A few issues to address before merging.

🔴 Critical

Data migration missing — existing sections data will be silently dropped

PR #1900 introduced sections to MongoDB. This PR renames to containers with no migration. Any dashboards saved after #1900 are stored as { sections: [...] } in MongoDB. After this deploy, those documents still have sections in the DB but the app now reads dashboard.containers, which will be undefined — users lose their sections silently.

Fix with a read-time fallback in the API layer or a one-time migration:

// packages/api/src/models/dashboard.ts or the dashboard controller
containers: { type: mongoose.Schema.Types.Array, required: false },
sections: { type: mongoose.Schema.Types.Array, required: false }, // keep for migration

Or a read-time normalizer in the API response handler:

// When returning a dashboard, normalize legacy field
if (!dashboard.containers && (dashboard as any).sections) {
  dashboard.containers = (dashboard as any).sections;
}

Fix this →


type field is required but breaks Zod parse for containers saved via #1900

DashboardContainerSchema now requires type: z.enum(['section']). Any container documents stored by the previous PR (which didn't have a type field) will fail Zod validation. This is separate from the field rename — it would affect even if the field was still called sections.

Consider making it optional with a default: type: z.enum(['section']).default('section'), or at minimum add a .transform() to fill it in on read.

Fix this →


🟡 Warnings

onToggle and onToggleDefaultCollapsed are functionally identical (packages/app/src/DBDashboardPage.tsx:1677–1684)

Both call handleToggleSection(section.id) which just flips collapsed. There's no semantic difference between "click chevron" and "Collapse by Default" — both toggle the same persisted collapsed field. The prop name onToggleDefaultCollapsed implies it sets a default in some distinct way, but it doesn't. Either:

  • Document this explicitly in a code comment
  • Or use a single onToggle prop for both (remove onToggleDefaultCollapsed from SectionHeader)

The menu label already communicates intent ("Collapse by Default" / "Expand by Default"), so the UX isn't broken — just the prop API is misleading.


inputRef is declared and attached but never used (packages/app/src/components/SectionHeader.tsx:33)

const inputRef = useRef<HTMLInputElement>(null); // line 33
// ...
<Input ref={inputRef} autoFocus ... /> // line 107

autoFocus already handles focusing on mount. inputRef is never called (no inputRef.current?.focus() or similar). Either remove it, or if it's intended for future use like inputRef.current?.select(), add that call now.


Accessibility: outer <Flex> has cursor: 'pointer' but is non-interactive (packages/app/src/components/SectionHeader.tsx:63)

The outer container sets cursor: 'pointer' via style={{ cursor: 'pointer', ... }}, but role="button", tabIndex, onClick, and onKeyDown are all on the inner <Flex>. Hovering over the padding area or the action icons in the outer container shows a pointer cursor, implying clickability where there is none. Change the outer style to cursor: 'default' and let the inner role="button" element use cursor: 'pointer'.


🔵 Minor / Suggestions

No confirmation on "Delete Section" (packages/app/src/DBDashboardPage.tsx:1681)

useConfirm is already used for tile deletion (line 1115) and saving flows, but section delete fires immediately. Given the destructive nature (removing the container, affecting potentially many tiles), a confirm prompt would match the existing delete patterns.


"New Section" title always hardcoded (packages/app/src/DBDashboardPage.tsx:1243)

Multiple rapid "Add Section" calls produce multiple containers all titled "New Section". Since IDs are unique this isn't a functional bug, but a minor quality-of-life improvement would be to auto-increment: New Section ${sections.length + 1}.


"Move to Section" menu appears even if only one section exists and tile is in it

availableSections.length > 0 shows the menu, but if sections.length === 1 and chart.containerId === sections[0].id, the dropdown only contains "(Ungrouped)". That's correct UX-wise, but worth a comment to explain why the single-section case is still useful.


✅ What's good

  • Schema design is solid — type enum for future discriminated union is exactly right
  • tilesByContainerId orphan handling (line 1297: !tilesByContainerId.has(t.containerId)) prevents tiles from being silently hidden if a section is deleted mid-session
  • Hover-only controls keep view mode clean
  • 14 new unit tests covering all authoring operations — good coverage
  • convertToDashboard{Template,Document} in utils.ts correctly propagates containers field to import/export

@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Addressing code review feedback

Fixed (6a45d25)

  • Removed unused inputRefautoFocus on <Input> already handles focus, ref was never called
  • Fixed accessibility: cursor: 'pointer' moved to inner interactive <Flex> — outer container no longer implies clickability on non-interactive areas (padding, action icons)
  • Removed redundant onToggleDefaultCollapsed prop — was identical to onToggle (both called handleToggleSection). The "Collapse/Expand by Default" menu item now calls onToggle directly. The menu label communicates the intent; a separate prop was misleading.

Deferred

Not changing

  • Delete confirmation — Noted, will add in follow-up (tracked in Dashboard: Sections, groups with tabs, and panel organization #1929 checklist)
  • "New Section" auto-increment naming — Minor polish, not blocking
  • Single-section "Move to Section" menu — Shows "(Ungrouped)" which is correct UX; dropdown is still useful for removing tile from the only section

@alex-fedotyev alex-fedotyev requested review from a team and fleon and removed request for a team March 19, 2026 15:12
Copy link
Copy Markdown
Contributor

@pulpdrew pulpdrew left a comment

Choose a reason for hiding this comment

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

LGTM, with a couple of non-code-related suggestions

Comment on lines +1535 to +1541
<Menu.Item
data-testid="add-new-section-button"
leftSection={<IconLayoutList size={16} />}
onClick={handleAddSection}
>
Add Section
</Menu.Item>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One bit of feedback - this is hard to find.

Maybe it could be a second button next to Add New Tile?

Image

Also, the Add New Tile button being below the sections while still adding the new tile above the sections is somewhat unintuitive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll propose improving this in a separate PR!

}
onClick={onToggle}
>
{section.collapsed ? 'Expand by Default' : 'Collapse by Default'}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This confuses me a bit, because it seems like this does the same thing as just expanding/collapsing the tile on the main dashboard.

IMO, expanding/collapsing should not be persisted to the dashboard UNLESS this option is used. But the existence of this option makes it more surprising that expanding/collapsing the section on the dashboard is also persisted. This seems to just make it harder to share a dashboard, if a relevant section is collapsed on page load. I think it would be nice to persist normal expand collapse states in the URL, and then fallback to the default state (saved in the DB based on this option here ^) if there is no URL state.

I do see the comment about it being intentional though.

// Intentionally persists collapsed state to the server via setDashboard
// (same pattern as tile drag/resize). This matches Grafana and Kibana
// behavior where collapsed state is saved with the dashboard for all viewers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I'll craft a separate PR for this!

@kodiakhq kodiakhq Bot merged commit b6cd088 into main Mar 20, 2026
13 of 14 checks passed
@kodiakhq kodiakhq Bot deleted the feat/collapsible-sections-authoring-ux branch March 20, 2026 16:04
kodiakhq Bot pushed a commit that referenced this pull request Mar 24, 2026
## Summary
- Section expand/collapse is now tracked in URL query params (`collapsed`/`expanded`) instead of persisting to the DB on every chevron click
- The DB-stored `collapsed` field on `DashboardContainer` becomes the default fallback — what viewers see when opening a dashboard fresh (no URL state)
- Chevron click updates URL state only (per-viewer, shareable via link)
- "Collapse by Default" / "Expand by Default" menu action in the section header saves to the DB (via `setDashboard`), setting the default for all viewers
- `SectionHeader` now accepts separate `collapsed`/`defaultCollapsed` props and `onToggle`/`onToggleDefaultCollapsed` handlers
- Adds 7 unit tests for `SectionHeader`

Implements Drew's [review feedback on PR #1926](#1926 (comment)):
> IMO, expanding/collapsing should not be persisted to the dashboard UNLESS this option is used. [...] I think it would be nice to persist normal expand collapse states in the URL, and then fallback to the default state (saved in the DB based on this option here) if there is no URL state.

## Demo

![Section collapse via URL params demo](https://github.com/hyperdxio/hyperdx/feat/url-based-collapse-state/docs/assets/collapse-url-state-demo.gif)

Shows: expanded sections → chevron click collapses first section (URL updates to `?collapsed=...`) → menu shows "Collapse by Default" (DB action, separate from view state)

## Test plan
- [x] Open a dashboard with sections — collapse/expand via chevron click, verify URL updates (`?collapsed=...` / `?expanded=...`) without saving to DB
- [x] Copy the URL with collapse state and open in a new tab — verify sections reflect the URL state
- [x] Open the section menu and click "Collapse by Default" — verify this saves to DB (persists after page refresh without URL params)
- [x] Verify "Expand by Default" / "Collapse by Default" label reflects the DB default, not current view state
- [x] Run `yarn ci:unit --testPathPatterns='SectionHeader'` — all 7 tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)
knudtty pushed a commit that referenced this pull request Apr 16, 2026
…ction (#1926)

## Summary

Adds the authoring experience for dashboard sections (create, rename, delete, manage tiles) and introduces a polymorphic `DashboardContainer` abstraction that future-proofs the schema for tabs and groups.

Builds on #1900 (core collapsible sections mechanics). Closes #1897.

### Schema: `DashboardSection` → `DashboardContainer`

- Renamed `DashboardSectionSchema` → `DashboardContainerSchema` with a new `type` field (`'section'` for now, extensible to `'group'` / `'tab'` later)
- `sectionId` → `containerId` on tiles
- `sections` → `containers` on dashboards
- Updated across all packages: common-utils types, API Mongoose model, app types, import/export utils

### Authoring UX

| Action | How |
|---|---|
| **Create section** | Dashboard `...` overflow menu → "Add Section" |
| **Rename section** | Click the title text directly (Kibana-style inline editing) |
| **Delete section** | Hover section header → `...` → Delete Section (tiles become ungrouped, not deleted) |
| **Collapse/expand** | Click section header chevron |
| **Toggle default state** | Hover header → `...` → Collapse/Expand by Default |
| **Add tile to section** | Hover section header → `+` button opens tile editor pre-assigned to that section |
| **Move tile to section** | Hover tile → grid icon → pick target section from dropdown |
| **Move tile out** | Same dropdown → "(Ungrouped)" |

![Mar-18-2026 16-37-58](https://github.com/user-attachments/assets/79e23773-db49-401d-8453-40e0461f6147)


### UX polish (informed by best practices research)

- **Click-to-rename** — click section title text to edit inline (no menu navigation needed)
- **Hover-only controls** — `...` menu and `+` button only appear on section header hover, keeping view mode clean
- **"Add Section" demoted** — moved from equal-sized button to dashboard overflow menu (section creation is less frequent than tile creation)
- **"Move to Section" reordered** — placed before delete button for discoverability, uses `IconLayoutList` instead of `IconFolders`

### What's NOT in this PR (follow-up work)

- **Drag tiles between sections** — needs `react-dnd` custom drag layer; data model already supports it (`containerId` update)
- **Reorder sections** — needs sortable list library; data model supports it (array order)
- **Tabs / Groups** — new container types; just add to the `type` enum and build UIs

## Test plan

- [x] 30 unit tests pass (16 existing schema/grouping + 14 new authoring operations)
- [x] All 110 dashboard tests pass unchanged
- [x] ESLint clean
- [x] No TypeScript errors in changed files
- [x] Backward compatible — dashboards without containers render exactly as before

🤖 Generated with [Claude Code](https://claude.com/claude-code)
knudtty pushed a commit that referenced this pull request Apr 16, 2026
## Summary
- Section expand/collapse is now tracked in URL query params (`collapsed`/`expanded`) instead of persisting to the DB on every chevron click
- The DB-stored `collapsed` field on `DashboardContainer` becomes the default fallback — what viewers see when opening a dashboard fresh (no URL state)
- Chevron click updates URL state only (per-viewer, shareable via link)
- "Collapse by Default" / "Expand by Default" menu action in the section header saves to the DB (via `setDashboard`), setting the default for all viewers
- `SectionHeader` now accepts separate `collapsed`/`defaultCollapsed` props and `onToggle`/`onToggleDefaultCollapsed` handlers
- Adds 7 unit tests for `SectionHeader`

Implements Drew's [review feedback on PR #1926](#1926 (comment)):
> IMO, expanding/collapsing should not be persisted to the dashboard UNLESS this option is used. [...] I think it would be nice to persist normal expand collapse states in the URL, and then fallback to the default state (saved in the DB based on this option here) if there is no URL state.

## Demo

![Section collapse via URL params demo](https://github.com/hyperdxio/hyperdx/feat/url-based-collapse-state/docs/assets/collapse-url-state-demo.gif)

Shows: expanded sections → chevron click collapses first section (URL updates to `?collapsed=...`) → menu shows "Collapse by Default" (DB action, separate from view state)

## Test plan
- [x] Open a dashboard with sections — collapse/expand via chevron click, verify URL updates (`?collapsed=...` / `?expanded=...`) without saving to DB
- [x] Copy the URL with collapse state and open in a new tab — verify sections reflect the URL state
- [x] Open the section menu and click "Collapse by Default" — verify this saves to DB (persists after page refresh without URL params)
- [x] Verify "Expand by Default" / "Collapse by Default" label reflects the DB default, not current view state
- [x] Run `yarn ci:unit --testPathPatterns='SectionHeader'` — all 7 tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Copilot AI pushed a commit that referenced this pull request Apr 20, 2026
…ction (#1926)

## Summary

Adds the authoring experience for dashboard sections (create, rename, delete, manage tiles) and introduces a polymorphic `DashboardContainer` abstraction that future-proofs the schema for tabs and groups.

Builds on #1900 (core collapsible sections mechanics). Closes #1897.

### Schema: `DashboardSection` → `DashboardContainer`

- Renamed `DashboardSectionSchema` → `DashboardContainerSchema` with a new `type` field (`'section'` for now, extensible to `'group'` / `'tab'` later)
- `sectionId` → `containerId` on tiles
- `sections` → `containers` on dashboards
- Updated across all packages: common-utils types, API Mongoose model, app types, import/export utils

### Authoring UX

| Action | How |
|---|---|
| **Create section** | Dashboard `...` overflow menu → "Add Section" |
| **Rename section** | Click the title text directly (Kibana-style inline editing) |
| **Delete section** | Hover section header → `...` → Delete Section (tiles become ungrouped, not deleted) |
| **Collapse/expand** | Click section header chevron |
| **Toggle default state** | Hover header → `...` → Collapse/Expand by Default |
| **Add tile to section** | Hover section header → `+` button opens tile editor pre-assigned to that section |
| **Move tile to section** | Hover tile → grid icon → pick target section from dropdown |
| **Move tile out** | Same dropdown → "(Ungrouped)" |

![Mar-18-2026 16-37-58](https://github.com/user-attachments/assets/79e23773-db49-401d-8453-40e0461f6147)


### UX polish (informed by best practices research)

- **Click-to-rename** — click section title text to edit inline (no menu navigation needed)
- **Hover-only controls** — `...` menu and `+` button only appear on section header hover, keeping view mode clean
- **"Add Section" demoted** — moved from equal-sized button to dashboard overflow menu (section creation is less frequent than tile creation)
- **"Move to Section" reordered** — placed before delete button for discoverability, uses `IconLayoutList` instead of `IconFolders`

### What's NOT in this PR (follow-up work)

- **Drag tiles between sections** — needs `react-dnd` custom drag layer; data model already supports it (`containerId` update)
- **Reorder sections** — needs sortable list library; data model supports it (array order)
- **Tabs / Groups** — new container types; just add to the `type` enum and build UIs

## Test plan

- [x] 30 unit tests pass (16 existing schema/grouping + 14 new authoring operations)
- [x] All 110 dashboard tests pass unchanged
- [x] ESLint clean
- [x] No TypeScript errors in changed files
- [x] Backward compatible — dashboards without containers render exactly as before

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: peter-leonov-ch <209667683+peter-leonov-ch@users.noreply.github.com>
Copilot AI pushed a commit that referenced this pull request Apr 20, 2026
## Summary
- Section expand/collapse is now tracked in URL query params (`collapsed`/`expanded`) instead of persisting to the DB on every chevron click
- The DB-stored `collapsed` field on `DashboardContainer` becomes the default fallback — what viewers see when opening a dashboard fresh (no URL state)
- Chevron click updates URL state only (per-viewer, shareable via link)
- "Collapse by Default" / "Expand by Default" menu action in the section header saves to the DB (via `setDashboard`), setting the default for all viewers
- `SectionHeader` now accepts separate `collapsed`/`defaultCollapsed` props and `onToggle`/`onToggleDefaultCollapsed` handlers
- Adds 7 unit tests for `SectionHeader`

Implements Drew's [review feedback on PR #1926](#1926 (comment)):
> IMO, expanding/collapsing should not be persisted to the dashboard UNLESS this option is used. [...] I think it would be nice to persist normal expand collapse states in the URL, and then fallback to the default state (saved in the DB based on this option here) if there is no URL state.

## Demo

![Section collapse via URL params demo](https://github.com/hyperdxio/hyperdx/feat/url-based-collapse-state/docs/assets/collapse-url-state-demo.gif)

Shows: expanded sections → chevron click collapses first section (URL updates to `?collapsed=...`) → menu shows "Collapse by Default" (DB action, separate from view state)

## Test plan
- [x] Open a dashboard with sections — collapse/expand via chevron click, verify URL updates (`?collapsed=...` / `?expanded=...`) without saving to DB
- [x] Copy the URL with collapse state and open in a new tab — verify sections reflect the URL state
- [x] Open the section menu and click "Collapse by Default" — verify this saves to DB (persists after page refresh without URL params)
- [x] Verify "Expand by Default" / "Collapse by Default" label reflects the DB default, not current view state
- [x] Run `yarn ci:unit --testPathPatterns='SectionHeader'` — all 7 tests pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: peter-leonov-ch <209667683+peter-leonov-ch@users.noreply.github.com>
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.

Dashboard: Collapsible sections - authoring UX

2 participants