The layout chain
A studio page such as/{slug}/clients renders through four layouts.
Root layout
app/layout.tsx is an async server component.
- Reads the locale with
getLocale()and the messages withgetMessages()from next-intl. - Sets
lang,dir(fromgetLocaleDirection(locale)),translate="no"and thenotranslateclass onhtml. The metadata also setsgoogle: notranslate, so the browser does not offer to translate the Hebrew UI. - Loads two Google fonts through
next/font: Figtree as--font-sansfor body text and Heebo as--font-heebofor headings. Heebo carries Hebrew glyphs, Figtree does not. - Imports
globals.cssand the cropper stylesheet.
Two progress bars are mounted:
NextTopLoader and the @bprogress provider. modules/shared/hooks/router.ts re-exports useRouter from @bprogress/next/app, and some components import useRouter from nextjs-toploader/app instead. Both wrap the Next router so programmatic navigation shows progress.
Authenticated layout
(authenticated)/layout.tsx exports dynamic = "force-dynamic" and revalidate = 0.
getSession(). No session redirects to/login.- Gets the request scoped QueryClient with
getServerQueryClient(). - Prefetches the session under
sessionQueryKeyand the organization list underorganizationListQueryKey. - Wraps children in a
HydrationBoundaryand these providers:
The last two are described in Shared components.
Main layout
(authenticated)/(main)/layout.tsx renders children unchanged. It only decides whether the user may enter. It runs userHasPassword() and getOrganizationList() in parallel, then applies the redirects listed in Route tree.
userHasPassword() returns true when the accounts call fails, so an API hiccup never traps a user on /set-password.
Account layout
(main)/(account)/layout.tsx picks the organization matching session.session.activeOrganizationId, or the first one, and loads its appearance so the account pages keep the studio’s colours and logo. With no organization it uses DEFAULT_STUDIO_APPEARANCE.
Organization layout
(main)/(organizations)/[organizationSlug]/layout.tsx:
getActiveOrganization(slug). A missing organization or a non member returnsnotFound().- Prefetches that organization under
activeOrganizationQueryKey({ slug })and wraps the subtree in its ownHydrationBoundary. This is what makes the sidebar render the right studio on the first paint. See Data fetching. getStudioAppearance(slug)returnsbranding(parsed from the studio record) andcalendarEnabled.- Reads the
perform-nav-collapsedcookie. - Renders
OrgThemeandAppWrapper.
OrgTheme
A server component in modules/shared/components/OrgTheme.tsx. It turns the studio’s Branding into a Google Fonts link and a style tag built by brandingCss() in modules/shared/lib/branding.ts. The style overrides design tokens at :root and .dark, so the studio colour reaches every component, including Radix portals.
AppWrapper
A client component in modules/shared/components/AppWrapper.tsx. It renders NavBar and the scrolling main element, and owns the sidebar open state.
- The collapse preference is a cookie (
NAV_COLLAPSED_COOKIE, value1when collapsed, one year). The server reads it and passesinitialNavCollapsed, so the first paint has the right width. - Settings routes (
settings,coaches,branding) always start expanded. A manual collapse there lasts until the user leaves the section. - Canvas routes start collapsed regardless of the preference: paths ending in
/automationsor/assistant, and any path containing/checkin/. - Board routes (
/clients,/workboard) makemaina full height flex column that scrolls inside itself. NavExpandedProviderexposes the state throughuseNavExpanded(). The plan builders pin their own chrome to the viewport and need it to stay aligned with the sidebar.
modules/shared/lib/nav-menu.ts: NAV_WIDTH_CLASS, NAV_OFFSET_CLASS and NAV_INSET_CLASS. The collapsed rail is 76px. Change all three together.
Other layouts
(authenticated)/not-found.tsx renders the 404 inside AppWrapper, so a wrong URL still shows the sidebar.
Page chrome
Two generations of page header are in use.PageContainer uses a border only. In page containers do not carry shadows. Floating layers such as dialogs and popovers keep theirs.
Things that exist but are not mounted
modules/shared/components/Topbar.tsx renders NotificationCenter, the bell with the unread count. No layout or page imports Topbar, so the notification center is not on screen in the current code.