---
module: platform.frontend-template
title: Frontend Template — starting a new SPA
tab: Overview
order: 55
audience: dev
---

> **Repo:** `hiveops-template` · **Live component demos:** `design-system.html` (open in a browser)
>
> Every HiveIQ SPA starts as a copy of this repo. It is a scaffold, not a deployed service — it has
> no endpoints, tables, or Kafka topics.

## Which page do I copy?

| Your page has… | Copy |
|---|---|
| severity/status badges (incidents, alerts, events) | `TemplatePage.svelte` |
| a plain list/table, no badges | `TablePage.svelte` |

**If the request does not make this obvious, ASK the user which one before copying. Do not guess.**
A page copied from the wrong base has to be rebuilt — the two differ in the badge colour maps and in
the filter-chip markup, not just in styling.

## COPY, do not rewrite

Never write a new page from scratch. Physically copy the canonical file, then edit it:

```bash
cp /source/hiveops-src/hiveops-template/frontend/src/components/TemplatePage.svelte \
   /source/hiveops-src/hiveops-MYAPP/frontend/src/components/MyPage.svelte
```

Only these things may change in the copied file:

1. Page `<h1>` title and `<p>` subtitle in the header
2. The `ALL_ITEMS` array and its interface → your data type and real API call
3. Table columns (`<th>` / `<td>`) → your fields
4. Filter `<select>` options → your filter values
5. The `SEV_DOT` / `STS_DOT` colour maps → your badge colours
6. The slide-in panel fields → your Add/Edit form fields

Never rewrite or replace these — they are tuned and they break silently:

| Component | Reason |
|---|---|
| `.filter-sidebar` HTML + CSS | Exact structure required; pills break the layout |
| `.active-filters` + `.filter-tag` chips | The `border-left-color` accent is specific |
| `.content-frame` + `.page-right` structure | Padding/gap must match the form-card |
| `.table-card` border | Always `1px solid #e5e7eb` — never override in dark mode |
| All `:global(.dark-mode)` rules | Tuned; do not remove or rewrite |
| `Pagination` placement | Top of `.table-card`, never below |

### Canonical spacing (code, not docs)

`.page-right` is `padding: 0.65rem; gap: 0.5rem`, and `.content-frame` carries **no** `margin-top` —
the `gap` provides that spacing. Setting both double-spaces the frame.

> The `hiveops-template/CLAUDE.md` table still says `padding: 1rem; gap: 0.75rem`. That is stale.
> The `.svelte` source is the truth.

### Filter sidebar defaults

`let filterSidebarCollapsed = true;` — the sidebar starts collapsed on every page. Light-mode filter
header CSS is identical across both templates. In dark mode the active-filter bar stays neutral
(`#111827`, border `#374151`) with a muted-gray `#9ca3af` label — it does **not** turn blue, and the
label and toggle button both need `!important` to hold that colour.

## Starting a new app

```bash
cp -r /source/hiveops-src/hiveops-template/frontend /source/hiveops-src/hiveops-MYAPP/frontend
```

Search `APPNAME` to find every placeholder, then edit:

- `frontend/package.json` → `"name": "hiveops-MYAPP-frontend"`
- `frontend/vite.config.ts` → `port:` (see below — **the template ships on 5179, which collides with claims**)
- `frontend/Dockerfile` → `EXPOSE` + `CMD` port
- `frontend/index.html` → `<title>`
- `frontend/src/App.svelte` → `FAV_KEY` localStorage key, imports, nav groups, views
- `build.sh` → `IMAGE_NAME="hiveiq-MYAPP-frontend"`

Do **not** remove `src/vite-env.d.ts`. It declares `__APP_VERSION__`, which `vite.config.ts` defines
from `package.json`; without it `svelte-check` errors. Delete `src/components/ExampleView/` once your
real views exist.

### Assigned dev ports

Read from each app's `vite.config.ts` (verified 2026-07-09), not from any CLAUDE.md:

| Port | App | Port | App |
|---|---|---|---|
| 5173 | incident | 5182 | reports |
| 5174 | analytics | 5183 | website |
| 5175 | recon | 5184 | profile |
| 5176 | fleet | 5185 | vault |
| 5177 | devices | 5186 | msp |
| 5178 | transactions | 5187 | aria |
| 5179 | claims | 5188 | guide |
| 5180 | adoons | 5190 | feedback |
| 5181 | dashboard | | |

**5189 is free; the next new app takes 5191.** The template itself is checked in on 5179 as a
placeholder — always change it as the first edit after copying.

## Verify before finishing

Open the page **in dark mode** and confirm all six:

1. Filter sidebar collapses/expands
2. Selecting a filter shows a colored chip ABOVE the content frame, with a visible gap
3. The chip has a thick colored left border — not a pill, not a badge
4. Toolbar (`N records` + action button) is INSIDE the white-bordered content frame
5. Pagination is at the TOP of the table, inside the frame
6. Sidebar labels (SEARCH, SEVERITY, STATUS) are muted gray, not white

Frontend work is never committed unverified — render it, let the user confirm, then commit.

## Gotchas

- **CSS lives in the Svelte `<style>` block.** Never `import './X.css'` — global CSS bleeds by Vite
  bundle order and the last-loaded duplicate class silently wins.
- **Dark mode needs explicit `tbody tr` backgrounds.** `overflow: auto` makes an opaque region; the
  parent card's dark background does not show through.
- **`td` font-size is always `var(--font-size-label)`** (0.85rem), never `--font-size-body`.
- **`html, body` must set `font-size: 14px`** — every `rem` in the design system depends on it.
- **Never add a local `.header {}` block** — it overrides the global gradient in `App.svelte`.
- **`toLowerCase()` before title-casing enums**, or `CARD_READER_FAIL` renders as all-caps.
