Status (2026-10-10): live on
main, and the only navigation. The old sidebar, its header, the legacy shell, theNEXT_PUBLIC_NEW_NAVswitch and the unused--sidebar-*CSS tokens were deleted at the founder's request (--sidebaritself stays: it is the header's and the bars' surface colour). Going back means reverting that commit.Design record and clickable prototypes: the "Vlozi Sidebar-less Nav Wireframes" canvas (frames 9 and 11 are the approved ones).
What changes for users
Tablet and desktop (768px and up): a two-row header.
- Row 1: the Vlozi mark (always links home), a workspace menu (switch, new workspace, the 3-of-3 limit), the plan tag, the credits pill, the notification bell, a ? menu (Documentation, Send feedback, Keyboard shortcuts) and the account menu (credits, Your account, Workspace settings, Light/Dark/System theme, Sign out).
- The plan tag (next to the workspace) names the plan, and turns into the alert when something
needs doing: "Pro trial · 3 days left" (coral dot in the last week), "Payment due" (red),
"Starter · ends 3 Nov". A coral Upgrade appears beside it only when it matters: on Free, under
20% of the month's credits ("Starter · 12% credits"), or in a trial's last week ("Keep Pro"). It
carries the next plan's real yearly saving ("−17%"). The tag opens a card: credits against the
month's allotment, the next plan and its price, the yearly price, Upgrade and Billing. People
without billing access see the tag alone. On phones the plan shows under the workspace name and
Upgrade sits in the account menu. Rules:
src/lib/plan-status.ts. - Row 2: Home · the current service (coral) · the switch button ⊞ · that service's pages as tabs. Pages that don't fit go behind +N; the page you're on always keeps a visible tab.
- At Home the Home button and the coral button are one, "⌂ Home" (Home has no pages of its own). The rest of row 2 shows Recent: the services you opened lately, each landing on the page you last used there (chips that don't fit are left out). ⚙ Settings sits at the right end.
- Home page: "Needs you" first (the bell's list: chats waiting, paused campaigns, insights, billing alerts…), then every service you can open as a small tile. No page title; the coral button names it.
- Workspace Settings is a section of its own. On
/dashboard/settings/*the coral button reads "Settings" and the tabs are its sections (General, Team, Billing, API keys, Services, Website), filtered by the person's permissions. "Back to Settings" works like any other service. - Integrations is no longer in the nav (founder, 2026-10-09). The page stays: connecting an account returns there, and it counts as Home.
- ⊞ flips only the tabs area over to show every service as tiles. They wrap onto as many lines as
needed, never scroll, and hang over a lightly dimmed page.
- ⚙ Settings has a fixed corner at the top right of the flip, solid dark; filtering never moves it. Typing "set" + Enter opens it when no service matches.
- Type to filter; Enter opens the first match; Esc or a click outside closes.
- Double-click ⊞ jumps back to the previous service. A "Back to …" tile does the same from the keyboard.
- The key hints ("Arrow keys move · Enter opens…") show only when it was opened with Ctrl/⌘+\.
- Ctrl+\ (⌘+\ on a Mac) opens the switcher. It's the old sidebar key.
Phones (under 768px): no second row.
- The header shows the service name; tapping it opens the service sheet.
- A coral floating button sits above the bottom bar:
- tap: the service sheet
- hold: a speed dial (Search, Home, Back, Switch)
- double-tap: back to the previous service
- When the button hides: it stays off editors and the console screens (Mailbox inbox, AI Copilot, Chatbot Sandbox, Contacts Playground). It tucks away while you type, while you scroll down, and while a page shows its own bottom bar.
- The bottom bar keeps the service's pages: four, plus More. Home has none, so no bar there.
- The service sheet has ⚙ Settings next to its search box.
Everywhere:
- Each service remembers the last page you opened in it, per workspace (browser storage only).
- Switching workspace takes you to the same section in the new workspace, or to Overview if the new workspace doesn't have that service.
- Editors keep row 1 only. These are the newsletter template and campaign editors, the blog editor, the content composer and the OAuth consent screen. Row 1 there scrolls away, exactly as the old header did.
Rollout (done)
- 2026-10-10: the new navigation became the default, then the only one. Deleted:
components/app-sidebar.tsx,components/dashboard-header.tsx,components/ui/sidebar.tsx,components/shell/legacy-dashboard-shell.tsx,lib/flags.ts, and the--sidebar-*tokens other than--sidebar. The Mailbox folder bar lost its in-page ("inline") mode, which only the sidebar layout used.
How it's built
| Piece | Where |
|---|---|
| Navigation rules: allowed services, active service and tab, editor/console routes, memory | src/lib/dashboard-nav.ts |
| One provider for everything that navigates | src/components/shell/dashboard-nav-provider.tsx |
| The shell | src/components/shell/dashboard-shell.tsx |
| Row 1, phone header | src/components/shell/header/ |
| Row 2, tabs, flip | src/components/shell/service-bar/ |
| Phone sheet, pages sheet, floating button | src/components/shell/mobile/ |
| "+N" maths (shared with the Mailbox toolbar) | src/lib/priority-fit.ts, src/hooks/use-priority-fit.ts |
| A service's own row 2 (Mailbox's folders) instead of page tabs | src/components/shell/service-bar/service-bars.tsx |
| Shared flip pieces (the ⊞ switcher and Mailbox's mailbox flip) | src/components/shell/flip/ |
Page heights. Full-height pages size themselves from three CSS variables set by the shell:
--dash-header-h, --dash-sticky-h and --dash-bottom-gutter. Never hard-code the header height
again. The pages that use them today: Mailbox, AI Copilot, Chatbot Sandbox, Contacts Playground,
the Forms builder preview and the content-engine Activity day headers.
A page with its own fixed bottom bar calls useSuppressFab(active) so the phone button steps
aside. The Media bulk-action bar is the first user.
A page that replaces the phone bottom bar is listed in hasOwnBottomBar() (dashboard-nav.ts).
In the new shell the page bar then yields and --dash-bottom-gutter is 0, so the page draws its own
bar inside its own height. Only the Mailbox inbox does this (Inbox · mailboxes · Folders · Compose;
see mailbox-redesign.md).
A page that needs row 2's height calls useHideServiceBar(active) (src/hooks/use-service-bar-hidden.ts).
The header then shows row 1 only and --dash-header-h shrinks with it. The Mailbox reader uses it
while a conversation is open.
Before flipping it on: manual checks
Automated: unit, component and accessibility tests for every piece (vitest + axe), plus a production build with the switch both off and on. Still needs eyes on real screens:
- Widths 360, 390, 768, 820, 1024, 1280 and 1440:
- row 1 fits at 768
- "+N" appears and disappears on resize
- no sideways page scroll
- switcher tiles wrap with no inner scroll
- Full-height pages:
- Mailbox: no page scroll, and the reply box stays above the phone keyboard
- AI Copilot, Chatbot Sandbox, Contacts Playground
- Forms builder preview, content-engine Activity
- newsletter template and campaign editors, blog editor, content composer
- The flip:
- double-click ⊞ goes back
- Ctrl/⌘+\ toggles
- Esc returns focus to ⊞
- reduced motion makes the flip instant
- Phone floating button:
- tap, hold and double-tap work with a real thumb
- it hides while scrolling and typing
- it's absent on consoles and editors
- the first-run hint stays dismissed
- A teammate with a narrow custom role sees only their services, and a forbidden deep link shows "No access".
- Workspace switch: you land in the right place, and the 3-of-3 limit shows.
- Dark mode and every theme preset: the coral accent follows the preset.
Gap log
Found while building this. Fixed means fixed on the branch, in the commit named.
Fixed
| # | Gap | Fixed in |
|---|---|---|
| F1 | Feedback said "Thanks!" even when the server rejected it, losing the message. Its textbox had no accessible name. Its "F" key hint did nothing. | feedback: split the form out… |
| F2 | The Overview cards showed ⌘-letter shortcut hints that nothing listened for. Most were browser shortcuts that could never work. | dashboard: drop dead shortcut hints… |
| F3 | The sidebar and bottom bar picked the active service by first startsWith match, in session order: /dashboard/mediafoo counted as Media. Tab highlighting depended on the order of the registry's navItems. |
dashboard: one navigation provider… |
| F4 | The phone bottom bar showed no active tab when the current page was past the first four. The Engagement inbox count was missing on phones. | mobile: bottom bar's More… |
| F5 | The Forms builder preview stuck under the 56px header, hiding its top. | dashboard: size full-height pages… |
| F6 | Switching workspace left you on the old workspace's page, often a record that doesn't exist. switchWorkspace didn't report success. |
header: row 1… |
| F7 | The workspace limit was a bare 3 in the sidebar and a separate constant in /workspace/new. |
header: row 1… |
| F8 | The theme toggle did nothing on its first click from the default System setting on a dark OS. It's now a Light/Dark/System choice. | header: row 1… |
| F9 | The Mailbox toolbar's "+N" popover was a dialog with no accessible name (axe: serious). | header: row 2… |
| F10 | The blog editor and content composer each rendered a second <main> landmark. |
a11y: one main landmark… |
| F11 | ThemePresetProvider read localStorage unguarded at the app root. Browsers that block site data crashed the whole app. | theme: don't take the app down… |
| F12 | Branch review: the desktop switcher reopened on browser Back to the page it was opened on, and picking the current page left it open. | nav: fixes from the branch review |
| F13 | Branch review: the new header was 1px taller than --dash-header-h, so Mailbox scrolled the document by a pixel. |
nav: fixes from the branch review |
| F14 | Branch review: closing the switcher with Ctrl/⌘+\ left keyboard focus on <body>. |
nav: fixes from the branch review |
| F15 | Branch review: badge counts were polled from every page. Now only the current service's are polled; the rest refresh when the switcher opens. | nav: fixes from the branch review |
| F16 | Branch review: the phone hint couldn't be dismissed with storage blocked. A stale feedback timer could close a reopened dialog. | nav: fixes from the branch review |
Open (not done in this branch)
| # | Gap | Owner / next step |
|---|---|---|
| G1 | eslint src reports 211 problems (135 errors) on main, so lint can't gate CI. One example: ActivityView.tsx sets state synchronously in an effect. The new code adds none. |
Dashboard: burn down, then add lint to CI. |
| G2 | AI Copilot, Chatbot Sandbox and Contacts Playground subtract an extra 3.5rem (the phone bottom bar) on desktop too, so they likely leave a 56px gap at the bottom. Kept as-is (same values as before). | Check on screen; switch to --dash-bottom-gutter if confirmed. |
| G3 | Only Engagement has a live badge count. Mailbox unread and Forms new-submission counts need count endpoints. | Mailbox / Forms services. |
| G4 | There is no app-wide search; Mailbox and Content Engine each own Ctrl/⌘+K. | A separate project; it must defer to the module palettes. |
Closed, no change needed (2026-10-09). The ad-hoc localStorage helpers (forms seen-counts, mailbox drafts) already guard storage. Forms' version keeps the good entries and drops only the bad ones, which browser-store's whole-value guard would make worse. |
— | |
| G11 | Home's "Get started" checklist for new workspaces (canvas frame 14C) was left out: several steps have no reliable "done" signal yet (invited a teammate, created a first form…). | Dashboard + manager: expose per-workspace setup state, then add the checklist above "Needs you". |
| G12 | The "price drop alert" uses each plan's real yearly discount. Limited-time offers ("₹2,499 → ₹1,999 · 3 days left") need an offers system first: an offers table in billing, Razorpay offer/discount IDs at checkout, expiry, and a bell item when one starts. | Manager (billing): build offers; then the plan tag shows them. |
Fixed 2026-10-09 (settings: its own section…): workspace Settings works like a service. Row 2 (and the phone bottom bar) shows General · Team · Billing · API keys · Services · Website, each shown only to people whose role can open it. The card grid and the "← Settings" back links are gone. The personal account area moved to "Your account" in the avatar menu. |
— | |
| G8 | The notification bell is v1: counts only, built from what services already expose. It has no history ("post published", "campaign sent"), no realtime and no per-person read state. The vlozi-events queue has every event but only Flows consumes it. |
A notification service: a second queue consumer and a feed table, with Mailbox's DO+WebSocket pattern for live updates. |
| G9 | Billing types limit_warning and addon_expiring alerts, but the backend never sends them, and nothing warns about low credits. The bell would show them as soon as they exist. |
Manager: emit them, and pick a low-credit threshold. |
Fixed 2026-10-09 (bell deep links land filtered…): the Chatbot inbox and Newsletter campaigns honour ?status=, the bell links there, and Brain's Overview marks the insights it shows as seen. Original note: Brain insights are never marked seen in the app: the Overview page shows them without calling /seen. The bell marks one seen when you open it from there. The Chatbot inbox and Newsletter campaigns pages don't read a status filter from the URL, so the bell can't open them pre-filtered. |
Brain / Chatbot / Newsletter modules. | |
| G7 | The workspace limit exists in two places: lib/workspace-limits.ts mirrors apps/manager's FREE_WORKSPACE_LIMIT. |
Serve it from the manager API when the limit becomes plan-based. |