Skip to content
Starters

Starters

A starter is a blank, buyer-facing version of one edition: the same shell, theming, density, layout, direction, i18n and command palette (navigation and appearance only — no record search, no bell, no RBAC), with every demo screen, the mock API and their dependencies already removed. There is nothing left to strip before your first real screen: a starter has no demo data, no auth and no record search to begin with, because tools/sync-shared.mjs never copies shared/mock/, shared/auth/, shared/forms/, shared/billing/, shared/inbox/, shared/analytics/, shared/calendar/, shared/guide/, shared/notifications/, shared/charts/ or shared/search/{view,commands} into a starter mirror in the first place (tools/starter/subset.mjs:1-10).

Starting one

Copy starters/<name>/ out of this repository into wherever your project will live, then install and build it standalone, from its own lockfile, the same way its full edition does. Copy it out rather than editing it in place: inside this repository, tools/starter/build.mjs regenerates every starter from its edition and overlays, and --check fails on a starter that no longer matches what it would generate.

HTML

In starters/html
pnpm install --frozen-lockfile
pnpm run build

React

In starters/react
pnpm install --frozen-lockfile
pnpm run build

Next.js

In starters/nextjs
pnpm install --frozen-lockfile
pnpm run typecheck
pnpm run build

Vue

In starters/vue
pnpm install --frozen-lockfile
pnpm run build

Nuxt

In starters/nuxt
pnpm install --frozen-lockfile
pnpm run typecheck
pnpm run build

HTML starter

Demo screens (orders, customers, pipeline, inbox, calendar, analytics, billing…), the mock API and its browser journal, charts, the notification bell, record search, sign-in pages and role-based access. Add a real backend once your first screen needs one, and read it through fetch the way assets/js/app.js reads nothing today.

  1. Add pages/orders.html, with front matter: <!--@ title: Orders | layout: shell | nav: orders | crumbs: Commerce @-->.
  2. Add a link in partials/nav.html, wrapped in {{#nav-orders}}…{{/nav-orders}} so it highlights when active — the palette reads its pages straight off this sidebar, so there is nothing else to wire up.
  3. Run pnpm run build again.

Add the key to shared/locales/en.json. The mirror also carries es, pt-BR and ar for parity with the four framework starters, but this edition has no language switcher — it only ever renders en.

React starter

Demo screens, the mock API, charts, the notification bell, record search inside the palette, sign-in and RBAC, and TanStack Query. Add Query back the day a screen actually fetches something.

  1. Add the page under src/pages/, in the shape src/pages/Home.tsx already is: a component, no data of its own to start.
  2. Give it a route in src/app/App.tsx, inside <AppShell>, the way / and the catch-all already are.
  3. Add an entry to src/app/nav.ts — the sidebar and the command palette both read this one list, so a page shows up in both from a single edit.
  4. Add the two keys the entry needs (its key and, if the nav item is new to a section, the section's key) to all four catalogues under src/shared/locales/: en.json, es.json, pt-BR.json, ar.json. A missing key renders as the raw key rather than failing.

Next.js starter

Demo screens, the mock API, charts, the notifications bell, record search, auth and RBAC, and TanStack Query. Add Query back when your first screen fetches from a real API — the demo edition's src/layout/palette.tsx shows the shape of a debounced, cancellable fetch if you need one for search later.

  1. Add a page: src/app/[locale]/orders/page.tsx.

  2. Add it to the nav (and the palette, which reads the same list):

    // src/layout/sections.ts
    export const SECTIONS: NavGroup[] = [
      {
        key: 'nav.overview',
        items: [
          { key: 'nav.dashboard', icon: 'layout-dashboard', path: '/' },
          { key: 'nav.orders', icon: 'shopping-bag', path: '/orders' },
        ],
      },
    ]
  3. Add nav.orders (and any other new keys) to all four catalogues under shared/locales/ at the repository root, then re-run pnpm dev in the full Orion checkout so tools/sync-shared.mjs copies them down — or, once this starter is on its own, add the keys straight to src/shared/locales/*.json.

Vue starter

Demo screens, the mock API, charts, the notification bell, record search, authentication, RBAC and TanStack Query. Add a query library when your first screen actually fetches something.

  1. Create the page, e.g. src/pages/Reports.vue.
  2. Add a route in src/router.ts, with a meta.title and meta.crumbs catalogue key:
    { path: '/reports', name: 'reports', component: Reports, meta: { title: 'nav.reports', crumbs: 'nav.overview' } }
  3. Add the sidebar/palette entry in src/app/nav.ts:
    { key: 'nav.reports', icon: 'chart-column', path: '/reports' }
    The command palette picks it up automatically — it reads the same list.
  4. Add nav.reports (and any other new keys) to all four catalogues in src/shared/locales/.

A route-only change to src/App.vue or src/router.ts (adding a route, not touching the shell logic) is a legitimate reason to re-pin this starter's overlay with tools/starter/build.mjs --repin vue <path>; it does not mean the overlay drifted from the edition.

Nuxt starter

Every demo screen (orders, customers, products, invoices, analytics, calendar, inbox, settings), the mock API, ApexCharts, the notification bell, the topbar's record-search combobox, and auth/RBAC — there is no signed-in role, so the sidebar and the palette show every page there is. Wire your own backend and add data fetching where you need it.

  1. Create the page, e.g. app/pages/orders.vue. Give it definePageMeta({ title: '...', crumbs: '...' }) if it needs its own heading, or leave it out to inherit the dashboard's.
  2. Add an entry to SECTIONS in app/utils/nav.ts. That one list feeds both the sidebar and the palette's navigate commands, so a page added there shows up in both with no extra wiring.
  3. Add any new catalogue key to the four files this starter's shared/ mirror carries at app/shared/locales/en.json, es.json, pt-BR.json and ar.json. If you are developing inside the Orion repository, edit the repository's own shared/locales/*.json instead and let the sync mirror them down.
  4. Fetch data with Nuxt's own useFetch/useAsyncData — nothing here reserves a spot for a separate query library.

How the starters are generated

starters/html, starters/react, starters/nextjs, starters/vue and starters/nuxt are blank, buyer-facing starting points for each edition: the same shell, theming, density, layout, direction, RTL, i18n and command palette (Cmd/Ctrl+K, navigation and appearance only — no record search, no bell, no RBAC), with every demo screen, the mock API and their dependencies removed. Each installs and runs standalone, from its own lockfile, the same way the edition it comes from does.

They are generated, never hand-edited, and their source is committed like any other edition's: only the shared mirrors (starters/*/**/shared/), the HTML starter's vendored bundle (starters/html/assets/vendor/), build output (dist/, .next/, .output/, node_modules/, …) and starters/nextjs/next-env.d.ts are gitignored — the same carve-outs each full edition has.

From README.md — Full repository only
node tools/starter/build.mjs                     # regenerate all five starters
node tools/starter/build.mjs react               # regenerate one, by name
node tools/starter/build.mjs --check             # every starter matches its manifest
node tools/starter/build.mjs --check --complete  # --check, plus every starter/mirror/release-smoke name list agrees
node tools/starter/build.mjs --repin react src/layout/Topbar.tsx  # take a new pin after reviewing an edition change

Each starter comes from a manifest (tools/starter/starters/<name>.json) naming what to copy from the edition verbatim, which dependencies to drop, and which files are overlays — a starter-only file, or a full replacement of an edition file, kept in tools/starter/overlay/<name>/**. An overlay that replaces an edition file records a truncated sha256 of that edition file (fromSha), so --check fails the moment the edition file changes underneath the pin — a starter can never drift silently from the edition it tracks. Review the edition's change, update the overlay if the starter needs to follow it, then take the new pin with --repin; a route-only change to the edition file, with nothing new for the starter to reconcile, is a legitimate reason to --repin without touching the overlay at all.

A starter's command palette is built from one nav list per starter (src/app/nav.ts for React/Vue, src/layout/sections.ts for Next.js, app/utils/nav.ts for Nuxt, the rendered sidebar for HTML) — the same list its sidebar renders, so a page added there shows up in both. node tools/release-smoke/run.mjs starters and node tools/smoke/starter-html.mjs (the HTML starter's file:// pass) prove this end to end: a clean install, a build, every route in every appearance, and an interaction pass that opens the palette, chooses an appearance and confirms it repaints rather than just flips an attribute, opens and closes the drawer, and — where the starter has a language switcher — proves that too.

After an edition's own lockfile changes, refresh the matching starter's lockfile the same way, then reinstall from it:

From README.md
cp <edition>/pnpm-lock.yaml starters/<edition>/pnpm-lock.yaml
(cd starters/<edition> && pnpm install)

Each starter's own README explains how to add its first screen.