UI & Pages

The shadcn/ui stack, command palette, layout, and built-in pages.

The renderer is a standard React SPA: Tailwind v4, shadcn/ui, react-router with lazy routes, a cmdk command palette, and sonner toasts. This page maps the UI layer and inventories the pages you inherit.

The stack

ConcernLibraryWhere
StylingTailwind v4 + tw-animate-cssapp/styles/globals.css
Componentsshadcn/ui (new-york style)app/components/ui/, components.json
Command palettecmdkapp/components/command-palette/
Toastssonnerapp/components/ui/ (Toaster) + use-toast
Animationframer-motiononboarding, transitions
Routingreact-router 7 (HashRouter)app/app.tsx

Routes use HashRouter — the renderer is served from a file:// document in packaged builds, where hash routing avoids server-side path resolution. Add shadcn components with pnpm dlx shadcn@latest add <component>; the new-york style and CSS-variable theming are already configured in components.json. See Theming for the accent-color layer.

Layout & sidebar

AppShell (app/components/layout/) wraps the authenticated pages with the custom titlebar, sidebar, and content area. Auth screens use ShellLessLayout instead. The sidebar is data-driven from sidebar-items.ts, grouped into Workspace, Template (showcase, flag-gated), and Application:

// app/components/layout/sidebar-items.ts
export const SIDEBAR_GROUPS = [
  { id: 'workspace', items: [{ id: 'home', to: '/home', /* ... */ }] },
  // [template-demo] Template group — present only when showcase is enabled
  { id: 'application', items: [/* billing (flag-gated), logs, help, settings, about */] },
]

Billing appears only when billingConfig.enabled, and the Template group only when showcaseConfig.enabled — so a stripped build has no dead entries.

Command palette

The palette opens with Cmd/Ctrl+K (an in-window shortcut, not a global hotkey — see Notifications & Shortcuts). Its contents come from command sources in command-sources.ts:

SourceProvides
routesSourceApp routes (Home, Settings, About, Account, Logs, Help, Update, Billing when enabled).
settingsSourceEvery searchable setting, deep-linking to its tab (/settings?tab=…).
recentFilesSourceRecently opened files.
showcaseSourceShowcase demos (flag-gated, [template-demo]).

Register your own commands from app code with registerCommandSource, which returns an unregister function:

import { registerCommandSource } from '@/app/components/command-palette/command-sources'

const off = registerCommandSource((ctx) => [
  { id: 'export', title: ctx.t('common:actions.export'), group: 'navigation', perform: () => {/* ... */} },
])

Page inventory

Routes are code-split (React.lazy) so the initial bundle carries only the shell; each page resolves on first navigation.

PageRouteWhat it is
Home/homeLanding dashboard: a rebrand checklist, neutral quick actions (Logs/Settings/Help), and — when showcase is on — the capability catalog.
Help/helpDocumentation link, feedback mailto, and a bug-report link to ${REPO_URL}/issues/new, plus a jump to Logs.
About/aboutApp/system diagnostics with a redacted "Copy diagnostics" button.
Update/updateUpdate status and manual check/download/install. See Auto Update.
Logs/logsLog file path + "Open log folder". See Observability.
Settings/settingsThe settings center. See Settings Store.
Account/accountProfile + avatar. See Example Features.
Billing/billingPlans and entitlement (flag-gated). See Billing.
Error / NotFound/error, *Themed ErrorPage and NotFoundPage.

The Help page's support surfaces are brand-driven: the feedback mailto uses SUPPORT_EMAIL and the bug report uses REPO_URL from shared/constants.ts, so rebranding redirects them automatically.

Onboarding

OnboardingGate (app/components/onboarding/) reads the persisted settings and shows a first-run flow until onboardingCompleted is set. It optionally gates on a EULA: leave EULA_URL empty in shared/constants.ts to ship without one (the default), or set it (with EULA_VERSION) to require acceptance — bumping the version forces re-acceptance. Completion is persisted through the settings store, so it shows once per install.

Adding a page

Add the route

Add a lazy import and a <Route> inside the AppShell element in app/app.tsx.

Add it to navigation

Add a sidebar-items.ts entry (and/or let routesSource surface it in the palette).

Add i18n keys

Add the nav label and page copy to app/locales/{en,zh}/*.json. See Internationalization.

(If deep-linkable) allow-list the route

Add it to navigationRouteSchema in event-schema.ts so a deep link can reach it. See Deep Links.

On this page