Files
shopdb-flask/frontend/DEVELOPMENT-STANDARDS.md
cproudlock 784964c907 Bring the wilm branch into main: endpoints plugin, agent, policies, CSV import
Ports the corporate-GitHub wilm branch onto main, keeping the two branches
separate. Thirteen upstream commits arrive with their authorship intact; the
review fixes and the portability work below are on top.

What arrives:

  endpoints plugin  - a ShopDB agent reporting endpoint state, telemetry,
                      services and interfaces, with a command channel the agent
                      long-polls. Ingest stays on the generic collector route
                      (ADR-006); the agent key is a UI-managed single-scope
                      token whose revocation is an instant kill switch.
  computers policies - Configuration and Business Policies (RMM-style), scope
                      trees validated at write time, actions checked against
                      the endpoints command allowlist. Feature-gated: every
                      route 404s while the setting is off, so enabling it can
                      never half-expose the surface.
  asset CSV import  - templates, a collector script carrying the site path, and
                      a validate-then-commit import. A plugin can own a virtual
                      column plus a post-write row hook, which is how a PC's
                      machine number becomes a controls link without a
                      hand-built relationships sheet.
  agent/            - the Windows agent and its installer.

Contract 0.21.0 -> 0.24.0, additive throughout: register_import_hook (0.22),
mint/list/revoke_service_token (0.23), collector_authorized (0.24). The stability
table had no rows for any of them and now does.

MySQL 5.6 portability. The branch stored three policy columns as sa.JSON, the
only JSON columns in the product, and 5.6 has no JSON type: the migration failed
with a bare syntax error near the word JSON. They are TEXT now, through a
JSONText type decorator, so all seventeen call sites still read and write dicts.
The convention elsewhere is already TEXT (guides.blocksjson,
geenforce.manifestjson).

Review fixes carried on top:

  - six policy GETs were jwt_required(optional=True), which authenticates a
    caller without checking anything; they require computers.view now, and six
    redundant jwt_required decorators are gone
  - the agent installer took a default BaseUrl and CollectorKey, so a mistyped
    run silently pointed an endpoint at the wrong instance; both are mandatory
    and a non-https BaseUrl throws unless -AllowInsecureHttp is passed
  - a committing CSV import wrote no audit row

Documentation: the 34 new routes are described in docs/api-inventory.json, which
is hand-maintained on purpose, and the generated spec now covers 331 paths and
477 operations across 22 surfaces. DEVELOPMENT-SETUP's 1071 troubleshooting row
said "use MySQL 8 for dev" and now gives the actual fix, including the trap that
5.6 downgrades ROW_FORMAT=DYNAMIC to Compact with a warning instead of an error.

Full suite 2123 passed, vitest 260 passed, all seventeen plugin chains migrate
clean on MySQL 5.6 and the naming, docs, parity and read-surface gates are green.
2026-08-28 16:17:17 -04:00

137 lines
6.0 KiB
Markdown

# Frontend Development Standards
## Naming Convention (LOCKED)
The shopdb-flask naming convention lives in the root `CONTRIBUTING.md`. Read it before naming any variable, component, API param, or CSS class.
Frontend-specific reminders pulled from the convention:
- Variables holding API field values: match the API field name exactly. Do NOT convert to camelCase. (`response.machineid`, NOT `response.machineId`)
- Pure JS variables: camelCase (`currentUser`, `isLoading`)
- Vue components: PascalCase, spelled out (`AssetDetail.vue`, `MachineForm.vue`)
- CSS classes: lowercase with dashes (`asset-detail`, `machine-form`)
- API params sent to backend: match DB column names without underscores (`params.locationid = 5`, NOT `params.location_id`)
- No emojis, em-dashes, smart quotes, or Unicode arrows anywhere. Plain ASCII only.
- Banned shorthand as standalone variables: `cfg`, `ctx`, `mgr`, `req`, `res`, `env`, `util`, `helper`. Spell them out (`canvasContext`, `response`, `manager`, etc.). Suffix usage like `printers_bp` is allowed.
Pre-commit hook at `scripts/check-naming-and-style.sh` enforces these rules.
## CSS Styling Standards
### Use CSS Variables for ALL Colors
**NEVER hardcode colors in component styles.** Always use CSS variables defined in `src/assets/style.css`.
Available variables:
```css
--primary /* Primary brand color */
--primary-dark /* Darker primary for hover states */
--secondary /* Secondary/muted color */
--success /* Success states (green) */
--warning /* Warning states (orange) */
--danger /* Error/danger states (red) */
--bg /* Page background */
--bg-card /* Card/panel background */
--text /* Primary text color */
--text-light /* Secondary/muted text */
--border /* Border color */
--link /* Link color (bright blue in dark mode) */
```
**Bad:**
```css
.my-card {
background: white;
color: #1a1a1a;
}
```
**Good:**
```css
.my-card {
background: var(--bg-card);
color: var(--text);
}
```
### Detail Pages - Use Global Styles
All detail pages (MachineDetail, PCDetail, PrinterDetail, ApplicationDetail) should use the **unified global styles** from `style.css`:
- `.detail-page` - Container wrapper
- `.hero-card` - Main hero section with image and info
- `.hero-image`, `.hero-content`, `.hero-title`, `.hero-meta`, `.hero-details`
- `.section-card` - Info sections
- `.section-title` - Section headers
- `.info-list`, `.info-row`, `.info-label`, `.info-value`
- `.content-grid`, `.content-column` - Two-column layout
- `.audit-footer` - Created/modified timestamps
**Only add scoped styles for page-specific elements** (e.g., supplies grid for printers, version list for applications).
### PrinterDetail.vue is the Master Template for Detail Pages
Use `PrinterDetail.vue` as the reference for new detail pages. Follow its structure and styling patterns.
### List Pages - Use Global Styles
All list pages should use the **unified global styles** from `style.css`:
- `.page-header` - Header with title and action button
- `.filters` - Search and filter controls
- `.card` - Main content container
- `.table-container` - Scrollable table wrapper
- `table`, `th`, `td` - Table styling
- `.pagination` - Page navigation
- `.badge`, `.badge-success`, etc. - Status badges
- `.actions` - Action button column
**PrintersList.vue is the Master Template for List Pages**
Use `PrintersList.vue` as the reference for new list pages. It has NO scoped styles - everything uses global CSS.
**Only add scoped styles for page-specific elements** (e.g., icon cells for applications, stats badge for knowledge base).
### Dark Mode Support
Dark mode is automatic via `@media (prefers-color-scheme: dark)`. Using CSS variables ensures colors adapt automatically - no extra work needed per page.
## Data Entry: Page or Modal
Both patterns are in use on purpose. Pick by what the record IS, not by how big the form feels today.
**Routed form page** (`views/<Thing>Form.vue` + a route) when the record has its own detail page and its own URL: machines, PCs, printers, network devices, measuring tools, USB devices, applications, knowledge-base articles, notifications, printed items. These forms are long, often carry an image upload, a map-position picker or relationship editing, and someone will want to link straight to one.
**Modal over its list** when the record is reference data that only exists inside the list it belongs to: every `*TypesList`, plus locations, vendors, models, VLANs, subnets, support teams, custom fields, API tokens, printer drivers, dashboard defaults. The form is a handful of fields, and keeping the list visible behind the dialog is the point.
Rule of thumb: **a thing with a detail page gets a form page; a lookup row gets a modal.**
Modal rules (from the overlay-close fix, commit d8fe0a4):
- A modal holding typed input must NOT close on overlay click or Escape. Losing a part-filled form to a stray click is not an acceptable failure.
- A confirmation dialog MAY close on overlay click (`@click.self`), since it holds nothing to lose.
- The panel background is `var(--bg-card-solid)`, never `var(--bg-card)`: the card variable is translucent in dark mode, which leaves a dialog see-through over the overlay. The shared `.modal` in `style.css` already does this; hand-rolled `.modal-panel` rules must too.
## Component Organization
- **Global styles**: `src/assets/style.css`
- **Page-specific styles**: Scoped `<style scoped>` block, only for unique elements
- **Font sizes**: Use `rem` units, base is 18px for readability on 1080p
## File Structure
```
src/
assets/
style.css # Global styles, CSS variables, detail page styles
views/
machines/
MachineDetail.vue # Uses global styles only
pcs/
PCDetail.vue # Global + PC-specific (app-list, etc.)
printers/
PrinterDetail.vue # Global + printer-specific (supplies-grid)
applications/
ApplicationDetail.vue # Global + app-specific (version-list, pc-list)
```