docs(guide): add platform.frontend-template module (#14)
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Failing after 31m32s

Guide had no coverage for hiveops-template, so the copy-don't-rewrite rule
and the TemplatePage-vs-TablePage decision were only in a stale CLAUDE.md.

- decision table for which page to copy; ask the user when not obvious
- assigned dev ports read from the actual vite configs (next free = 5191;
  CLAUDE.md still claims 5180, which is adoons)
- canonical .page-right spacing is 0.65rem/0.5rem, not the documented 1rem/0.75rem
- dark-mode filter-header values + collapsed-by-default sidebar

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 00:14:35 -04:00
parent 983037e3db
commit b047679927
@@ -0,0 +1,129 @@
---
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.