16 KiB
CLAUDE.md — BellSystems Control Panel v2
Instructions for Claude Code
Read this file at the start of every session. This is the v2 project — a clean rebuild. The old v1 code lives in
frontend/src/_archive/for reference only. Also readDESIGN.mdbefore writing any UI code.
Project Structure
C:\development\bellsystems-cp-v2\
│ CLAUDE.md ← you are here
│ DESIGN.md ← design rules, component contracts, page layout spec
│ docker-compose.yml
│
├── backend/ ← FastAPI backend — DO NOT MODIFY
│
└── frontend/
├── src/
│ ├── _archive/ ← v1 reference code — READ ONLY, never import from here except auth
│ ├── assets/
│ │ ├── global-icons/ ← action SVGs (edit, delete, download, etc.)
│ │ ├── side-menu-icons/ ← sidebar navigation SVGs
│ │ ├── comms/ ← communication type SVGs
│ │ ├── other-icons/ ← misc SVGs
│ │ └── customer-status/ ← CRM status SVGs
│ ├── components/
│ │ ├── ui/ ← design system components (the ONLY place to source UI)
│ │ ├── layout/ ← Sidebar, Header, MainLayout
│ │ └── shared/
│ ├── hooks/
│ ├── lib/
│ ├── modals/ ← all modal components live here, grouped by domain
│ ├── pages/ ← one file per page, grouped by domain
│ ├── providers/
│ ├── router/
│ │ └── index.jsx ← all routes defined here
│ ├── styles/
│ │ ├── tokens.css ← ALL design tokens (colors, fonts, spacing, shadows)
│ │ ├── components.css ← ALL component-level styles
│ │ └── global.css ← base resets, typography, scrollbar, .page-wrapper
│ └── main.jsx ← app entry point
└── vite.config.js
Project Overview
Bespoke SaaS Admin Console for BellSystems. Manages Devices, Customers (CRM), Manufacturing, Firmware, MQTT, Melodies, Staff, and more.
- Backend: FastAPI at
backend/— never modify - Archive:
frontend/src/_archive/— v1 reference, read-only - Active code:
frontend/src/(everything except_archive/) - Design rules:
DESIGN.mdat project root — read before writing any UI - Style Guide: live at
/dev/styleguide— shows every component with every variant - API client:
frontend/src/lib/api.jswraps_archive/api/client.js
Import Alias
@/ maps to frontend/src/:
import Button from '@/components/ui/Button' ✅
import Select from '@/components/ui/Select' ✅
import { useAuth } from '@/hooks/useAuth' ✅
import MainLayout from '@/components/layout/MainLayout' ✅
Never use relative ../ paths except inside providers/ and hooks/ when referencing _archive/.
The Golden Rules
- Never modify
_archive/— it is read-only reference material - All new code goes in
frontend/src/— no exceptions - No
/v2/prefix anywhere — routes start from/, imports start from@/ - Read
DESIGN.mdbefore writing any UI code - Source every UI element from
@/components/ui/— no raw HTML elements for styled things - Use only CSS tokens — never raw hex, rgb, or pixel values in component or page files
- Use
.masonry-gridfor all content pages with multiple variable-height sections — neverdisplay: gridwith fixed columns for card layouts. See DESIGN.md §11.
Available UI Components
Every component lives in frontend/src/components/ui/. These are the ONLY components to use.
Check the live Style Guide at /dev/styleguide to see all variants and states.
| Component | Import path | Purpose |
|---|---|---|
Button |
@/components/ui/Button |
All interactive actions |
StatusBadge |
@/components/ui/StatusBadge |
Coloured status pills |
FormField |
@/components/ui/FormField |
Every text/email/password/textarea input |
Select |
@/components/ui/Select |
Custom dropdown (used inside FormField type="select") |
Modal |
@/components/ui/Modal |
All overlay dialogs |
ConfirmDialog |
@/components/ui/ConfirmDialog |
Destructive / confirmation prompts |
DataTable |
@/components/ui/DataTable |
All tabular data with sorting/selection |
Pagination |
@/components/ui/Pagination |
Page controls beneath DataTable |
Spinner |
@/components/ui/Spinner |
Loading indicators |
PageHeader |
@/components/ui/PageHeader |
Page title block — every page starts with this |
Card |
@/components/ui/Card |
Contained content sections |
Tabs |
@/components/ui/Tabs |
Tabbed navigation within a page |
Toast |
@/components/ui/Toast |
Transient notifications (via useToast) |
SearchBar |
@/components/ui/SearchBar |
Search inputs with debounce |
Breadcrumbs |
@/components/ui/Breadcrumbs |
Navigation trail on detail pages |
Icon |
@/components/ui/Icon |
Inline SVG icons by name |
Folder Structure — Pages & Modals
Folders mirror the sidebar section hierarchy exactly.
frontend/src/pages/
├── auth/ ← unauthenticated routes (login)
├── dashboard/ ← General section
├── bellcloud/ ← Bell Cloud section
│ ├── devices/
│ │ └── notes/
│ ├── users/
│ ├── melodies/
│ │ └── archetypes/
│ └── mqtt/
├── crm/ ← Headquarters section
│ ├── comms/
│ │ └── mail/
│ ├── customers/
│ │ └── tabs/
│ ├── orders/
│ ├── quotations/
│ └── products/
├── engineering/ ← Engineering section
│ ├── manufacturing/
│ ├── firmware/
│ └── developer/
├── public/ ← Public / unauthenticated pages
│ ├── cloudflash/
│ └── serial/
├── settings/ ← Console Settings section
│ └── staff/
└── dev/ ← Internal dev tools (StyleGuide)
frontend/src/modals/
├── bellcloud/
│ ├── devices/
│ ├── melodies/
│ └── users/
├── crm/
│ └── products/
├── engineering/
│ └── manufacturing/
└── shared/
Page Layout — How Every Page Is Structured
Every authenticated page is rendered inside MainLayout, which provides:
- Sidebar — fixed left,
224pxwide (--sidebar-width) - Header — fixed top,
56pxtall (--header-height) - Content area — the remaining viewport space
Inside the content area, every page uses the .page-wrapper class (defined in global.css):
.page-wrapper {
flex: 1;
display: flex;
flex-direction: column;
padding: var(--space-12); /* 48px on all sides — desktop */
gap: var(--space-6); /* 24px between top-level sections */
min-width: 0;
}
@media (max-width: 768px) {
.page-wrapper {
padding: var(--space-8); /* 32px on mobile */
gap: var(--space-4);
}
}
This is the consistency guarantee: because every page uses .page-wrapper, the <PageHeader> title on every page starts at exactly the same position — 48px from the top and 48px from the left edge of the content area. Never override these paddings. Never add extra wrappers around page-wrapper that introduce additional offset.
Content width modes
Pages fall into two modes — choose based on how much content the page has:
Full-width (default — lists, tables, dashboards):
<div className="page-wrapper">
Centered (forms, settings, pages with very few items):
<div className="page-wrapper page-wrapper--centered">
Centered mode caps each direct child at --content-max-width-sm (640px) by default and centers it horizontally. Override when needed:
<div className="page-wrapper page-wrapper--centered"
style={{ '--page-content-max-width': 'var(--content-max-width-md)' }}>
Available tokens: --content-max-width-xs (480px), --content-max-width-sm (640px), --content-max-width-md (800px), --content-max-width-lg (1024px).
Rules for Every Page
Before writing any code
- Read
DESIGN.md— confirm tokens and components to use - Check
frontend/src/components/ui/— use existing components only - Check the Style Guide at
/dev/styleguidefor the correct variant/props - Check
frontend/src/_archive/for the equivalent v1 page — copy API calls and data shape only, never styling
Page template
// frontend/src/pages/[domain]/PageName.jsx
import PageHeader from '@/components/ui/PageHeader'
import { useAuth } from '@/hooks/useAuth'
// Import ONLY from @/components/ui/ — never raw HTML elements for styled things
export default function PageName() {
// 1. Auth
const { user } = useAuth()
// 2. State & data fetching
// 3. Event handlers
// 4. Render — always handle: loading, error, empty, data states
return (
<div className="page-wrapper">
<PageHeader title="Page Title" subtitle="Optional description">
{/* Action buttons — use <Button variant="primary"> etc. */}
</PageHeader>
{/* Page content — use Card, DataTable, Tabs, etc. */}
</div>
)
}
Styling rules
- Tailwind for layout only:
flex,grid,items-center,min-w-0, etc. - CSS token variables for ALL colors, spacing, typography —
var(--token-name) - No
.module.cssor per-page scoped CSS files - No
style={{ }}inline styles except for genuinely dynamic values (e.g. calculated widths) - No raw hex, rgb, or pixel values anywhere
Toolbar buttons — matching SearchBar height
.btn has line-height: 1 while .searchbar-input has line-height: var(--line-height-base) (1.5). This makes buttons shorter than the search bar by default. Whenever a <Button>, <SegmentedControl>, or <IconButtonGroup> sits in the same toolbar row as a <SearchBar>, all buttons must use padding-top/bottom: var(--space-3) and line-height: var(--line-height-base).
Already handled automatically (no extra props needed):
SegmentedControl—.seg-ctrl__btnincomponents.cssenforces--space-3padding andvar(--line-height-base)globally.IconButtonGroup—.icon-btn-group__btnincomponents.cssenforces--space-3padding globally.
Must be overridden manually — standalone <Button> in the same row as a <SearchBar>:
// Option A — inline style prop on the button:
<Button
size="md"
style={{ paddingTop: 'var(--space-3)', paddingBottom: 'var(--space-3)', lineHeight: 'var(--line-height-base)' }}
>
Label
</Button>
// Option B (preferred when multiple buttons share a toolbar) — scoped CSS block:
<>
<style>{`
.my-toolbar .btn {
padding-top: var(--space-3) !important;
padding-bottom: var(--space-3) !important;
line-height: var(--line-height-base) !important;
}
`}</style>
<div className="my-toolbar" style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
<IconButtonGroup … />
<Button variant="primary" size="md">Compose</Button>
</div>
</>
Date & time formatting
- Greek/European date style everywhere — DD/MM/YYYY, never US-style MM/DD/YYYY
- All date/time formatting must use
@/lib/formatters— never use rawtoLocaleDateString(),Intl.DateTimeFormat,toLocaleString(), ortoISOString().slice()in pages or modals - For
datetime-localinput values — usetoDatetimeLocal(iso)andnowLocal()from formatters. Never usenew Date(x).toISOString().slice(0, 16)— it converts to UTC and shifts the time (timezone bug) - Currency — use
fmtEuro(n)from formatters (Greek locale:1.250,00 €)
Available formatters (import { ... } from '@/lib/formatters'):
| Function | Output example | Use for |
|---|---|---|
fmtDate |
05/03/2026 |
Short numeric dates (tables, lists) |
fmtDateMedium |
5 Mar 2026 |
Medium dates (cards, details) |
fmtDateLong |
5 March 2026 |
Long dates (headings, summaries) |
fmtDateFull |
Wednesday, 5 March 2026 |
Dashboard, full context |
fmtDateTime |
5 March 2026, 2:30 pm |
Date + 12h time |
fmtDateTimeMedium |
5 Mar 2026, 14:30 |
Date + 24h time (compact) |
fmtDateTimeFull |
Wed, 5 Mar 2026, 2:30 pm |
Emails, comms |
fmtTime24 |
14:30:05 |
Time with seconds |
fmtRelative |
5 minutes ago |
Relative timestamps |
toDatetimeLocal |
2026-03-05T14:30 |
datetime-local input values |
nowLocal |
2026-03-05T14:30 |
Current time for form defaults |
toDateInput |
2026-03-05 |
date input values |
fmtEuro |
1.250,00 € |
Euro currency |
Data fetching
- Use
frontend/src/lib/api.js(wraps_archive/api/client.js) - Every data-fetching component must handle loading, error, and empty states
Modals
- Never defined inside page files
- Live in
frontend/src/modals/[sidebar-section]/[domain]/ModalName.jsx— mirror the pages folder structure - Pass data via props, actions via callbacks
Missing Components — Stop and Ask
If building a page requires a UI component that does not exist in frontend/src/components/ui/, Claude Code must STOP and say:
"I need a [ComponentName] component which doesn't exist yet. Please build it and add it to the StyleGuide before I continue."
Do NOT:
- Invent an inline one-off component inside a page file
- Use raw HTML elements styled with inline CSS as a substitute
- Proceed and leave a placeholder
The StyleGuide at frontend/src/pages/dev/StyleGuide.jsx is the source of truth for what components exist and how they look. Every component used in a page must have a visible example there first.
Building a New Page — Checklist
- File in correct
frontend/src/pages/[domain]/folder - Root element is
<div className="page-wrapper">— nothing else, nothing wrapping it - First child inside
page-wrapperis<PageHeader title="..."> - Only components from
frontend/src/components/ui/used - No raw hex colors or pixel spacing values anywhere
- Loading state implemented
- Error state implemented
- Empty state implemented
- Mobile responsive (375px minimum)
- Modals in
frontend/src/modals/ - Route added to
frontend/src/router/index.jsx
API Client
// frontend/src/lib/api.js
export { default } from '../_archive/api/client'
All pages import from @/lib/api, never directly from _archive.
Auth
frontend/src/hooks/useAuth.js re-exports from the archive AuthContext.
frontend/src/providers/AuthProvider.jsx re-exports the AuthProvider.
These are the ONLY two files permitted to import from _archive/auth/.
All other files use @/hooks/useAuth.