Repository navigation
feat(nav): nest the account rows under a Profile group in the sidebar (G31) #45
Description
Summary
On the web, Settings and Integrations live inside the profile/account menu. The native app inverts that: Settings is an unrelated ⌘, scene, "Integrations" does not exist as a concept, and the sidebar is ten flat peers with no account grouping at all.
The web's account menu (walked live 2026-09-07)
My User Page · Dashboard · Messages · My Organizations · Documents · Lists ·
Settings ▸ { Preferences, Integrations, Subscription } · Switch to ▸ · Log out
Proposal
Group the account-shaped sidebar rows under a Profile DisclosureGroup:
- My Profile (the new own-account page)
- Organizations (moved)
- Connections (moved)
- Settings
- Integrations (the new surface)
⚠️ HIG constraint — read before writing code
The App menu's Settings… (⌘,) item and SettingsRootView must keep working exactly as they do today. macOS convention is that Settings is its own window, and re-homing the panes into the detail column would break that expectation and the keyboard shortcut users already have.
So the sidebar rows are an additional entrance that opens the Settings scene via SettingsLink — never a relocation of the panes. Same for Integrations: the row opens the Settings scene on the Integrations tab.
Division of labor
App — navigation only. No Kit or Domain changes.
-
App/Navigation: introduce theProfiledisclosure group and moveOrganizationsandConnectionsunder it. -
My Profilerow → the own-account page. -
SettingsandIntegrationsrows →SettingsLink, landing on the right tab. - Every existing deep link that sets
selectionmust still land, expanding the parent group automatically — a deep link into Organizations cannot leave the user staring at a collapsed group. - Persist the group's expanded/collapsed state across launches.
- Decision 0003 still applies: no
import InterlinedKitunderApp/Navigation/**.
Tests
- happy — the group renders with all five rows,
⌘,still opens Settings - invalid — a deep link to a row inside a collapsed group expands it and selects the row
- upstream-failure — n/a (no I/O); instead assert the group renders when the current user is not yet loaded
- boundary — collapsed/expanded state survives a relaunch; selection survives a group toggle
Acceptance criteria
⌘,and File ▸ Settings behave identically to today.- No Settings pane is reachable only from the sidebar, and none only from
⌘,. - Full E2E gate green.
Notes
work-consolidation.md tracks this as G31. Size S–M. Do this after the My Profile page and the Integrations surface exist, otherwise the group has two dead rows.
Implementation plan (added 2026-09-15)
Sequenced as this issue asked — after My Profile (#44) and the Integrations surface (#47), so the group has no dead rows.
The group
Profile DisclosureGroup holding My Profile · Organizations · Connections · Settings · Integrations. The three reading surfaces that moved in were previously top-level peers; the seven that stayed (Search, Timeline, Scheduled, Notifications, Messages, Lists, Documents) are reading surfaces, not account management, and grouping them would be the web's IA applied where it does not fit.
The HIG constraint, honoured
⌘, and File ▸ Settings are untouched. The sidebar rows use SettingsLink, so they are an additional entrance to the same scene — three doors into one window, never a relocation of the panes into the detail column.
Landing on the right tab
SettingsLink opens the Settings window but cannot address a tab. So SettingsRootView's TabView gains a selection bound to @AppStorage("settings.selectedTab"), and the sidebar row stores its target before the link opens the scene.
Two details that follow from that:
- The Settings row targets Preferences explicitly rather than "whatever was open last" — a row labelled Settings that opens Crash Reporting reads as a bug.
SettingsTab's raw values are persisted, so renaming a case would silently send an existing user to a different pane. There is a test pinning them.
Deep links expanding the group
This issue's sharpest requirement: "a deep link into Organizations cannot leave the user staring at a collapsed group."
Every selection = … in MainWindowView — thirteen of them, across the menu commands, notification routing, search routing and share-link resolution — now goes through one select(_:) that opens the group first when the target lives inside it.
Expanding is deliberately one-way: a deep link to a top-level row does not collapse the group. The user's choice to keep it open is not something a menu command should undo.
MainWindowView.profileGroupSections is the single list that both the group's contents and select(_:) read, so a row cannot be moved into the group without deep links learning about it.
Persistence
@AppStorage("sidebar.profileGroup.expanded"), defaulting to expanded: on first run the three rows that moved in were previously top-level, and collapsing them by default would look like they had been removed.
Tests
Per the project's view-layer rule, the SwiftUI tree is not rendered. What is tested is the part with real logic and a real failure mode: the group's membership, that no section is orphaned by the partition, and that the persisted SettingsTab identities round-trip.
Acceptance
- ✅
⌘,and File ▸ Settings behave identically to today. - ✅ No Settings pane is reachable only from the sidebar, and none only from
⌘,. - ✅ Every deep link still lands, expanding the group when needed.
- ✅ Expanded state survives a relaunch.
Activity
Adron commentedon Sep 16, 2026
Implemented in PR #94 (G31). Work continues on the PR.
Sequenced after #44 and #47 as this issue asked, so the group has no dead rows.
The HIG constraint is honoured:
⌘,and File ▸ Settings are untouched, and the sidebar rows useSettingsLink— three doors into one window, never a relocation.The requirement with teeth — "a deep link into Organizations cannot leave the user staring at a collapsed group" — is met by routing all thirteen
selection = …assignments through oneselect(_:). Expanding is one-way: a deep link to a top-level row does not collapse the group.SettingsLinkcannot address a tab, soSettingsRootView'sTabViewgained a persisted selection the sidebar row sets first.