feat(design): add the operator design system the two surfaces share

The device console is a default-grey tkinter window with no hierarchy: no
grouping, no state colour, no type scale, the privilege warning buried in a
monospace paragraph, "Çık" duplicated at two sizes, and an output area that
looks editable. Every measurement is in the delivered work; the point here is
that the fix is a system, not a repaint.

Adds PRODUCT.md (product truth: who uses this, from where, under what
constraints), docs/DESIGN.md (the direction contract and the shared status
vocabulary) and web/static/tokens.css (the values both surfaces read).

The vocabulary is the load-bearing part. Four severities -- ok / warn / danger /
info -- plus one action accent, and one Turkish word per device stage
(KURULDU / KAYITLI / HAZIR / HATA / YOK) used identically on the device and in
the central panel. A fifth hue is a bug, and colour is never the only carrier:
every badge ships its word, so the fleet list stays readable in greyscale.

The ground is light on purpose. The console runs in workshops and dealer service
areas in daylight; a near-black ops console loses its own body text there and its
state colours collapse into equal-brightness smudges.

No webfont: the fleet installs offline from a committed pool, so a font CDN is a
broken page on every device.
This commit is contained in:
emirhanmamak-blueforce 2026-09-16 22:20:15 +03:00
parent d6d22c6e56
commit 4ad20452e7
4 changed files with 271 additions and 0 deletions

3
.gitignore vendored
View file

@ -51,6 +51,9 @@ packages.lock.tsv
# Documentation site (generated)
docs-site/node_modules/
# Design tooling scratch (local to a workstation, not shippable)
.impeccable/
docs-site/build/
docs-site/.docusaurus/
docs-site/docs/

69
PRODUCT.md Normal file
View file

@ -0,0 +1,69 @@
# Product
<!-- impeccable:product-schema 1 -->
## Platform
web
Two surfaces share this record. The **central panel** is a web surface (browser, server-rendered). The **device console** is a native desktop surface: a Python/tkinter application that ships on each field PC and runs with no network. Visual work on the device console follows the native-platform guidance for its own platform; this value names the surface under design here.
## Stack
Python standard library only (`http.server` + server-rendered templates). No framework and no third-party dependency: the fleet installs offline from a committed package pool, and `fastapi`/`uvicorn`/`flask` are not in that pool. Adding one would mean the panel cannot be installed on the devices it manages. Confirmed with the user over FastAPI for this reason.
## Users
**Field technician** — a dealer/authorised service person who receives a Windows 10 PC and must turn it into a Blueforce Field OS device. Works at the device, often in a workshop or a customer site, with limited time and no developer background. Job: run the install, know whether it succeeded, and recover when it did not.
**Central operator** — the person responsible for the whole fleet (~700 devices) from an office browser. Job: see which devices are installed or not, which rollout wave each is in, and push an approved update. Handles many devices per session and needs status, not detail.
Both operate the same fleet from opposite ends: the technician from the device, the operator from the browser.
## Product Purpose
Convert a fleet of Windows 10 field PCs into machines running Blueforce Field OS, and keep them correct afterwards. Success means a technician finishes an install without internet access, without guessing, and without a silent partial state — and an operator can say at any moment which devices are ready and which are not.
## Positioning
The install is provably offline and fail-closed. The device carries its own signed package pool, the installer refuses to continue when the contract does not hold rather than half-installing, and every destructive step demands explicit approval with a root gate. Neither property can be copied by a neighbouring tool that assumes internet access or treats a failed check as a warning.
## Operating Context
- Fleet: ~700 Windows 10 field PCs, converted in waves LAB(2) → P1(5) → P2(20) → W1(50) → W2(100) → PROD, production behind a separate typed override.
- Install target: Ubuntu 26.04.1 LTS ("Resolute Raccoon"), GNOME desktop installed but not the boot default — the device boots to a terminal and the desktop is switched on when required.
- Network: the field estate is reached over WireGuard; four remote channels are supported (SSH-over-WG, xRDP, RustDesk OSS, MeshCentral). No commercial remote-desktop licence (AnyDesk was rejected).
- Fleet tooling: Ansible CLI + Semaphore Community for orchestration, Prometheus + Node Exporter + Grafana OSS + Uptime Kuma for monitoring, Docusaurus for documentation.
- Device state machine: `PROVISIONED_OFFLINE → ENROLLED → READY`, with no skipping and no going back; `READY` is refused unless every channel reports `verified`.
- Documentation set: 33 numbered Turkish documents plus README/SUMMARY, served through Docusaurus.
## Capabilities and Constraints
**Confirmed functionality** — the device console exposes 48 operations: 13 write (all requiring root) and 35 read-only. The installer is `scripts/install/blueforce-install.sh`, driven by an offline bundle at `provisioning/offline-repo/built/` (index + `pool/` of 1321 `.deb` files, ~887 MB). Evidence of fleet state is an inventory and a enrolment token per device.
**Hard constraints** — no internet during install or normal operation; every user-visible string is Turkish (code, comments and commit messages stay English); destructive operations need an explicit typed confirmation and a root gate; a check that fails stops the operation, it never degrades to a warning.
**Deliberately undecided** — where the central panel is hosted and how it authenticates to the fleet. Not invented here: the panel is being designed before its deployment model is fixed.
## Brand Commitments
Name: **Blueforce Field OS**. Existing operator documentation and console copy are Turkish and already carry the product voice — plain, factual, no marketing register. No logo or palette is binding; no visual identity exists yet.
## Evidence on Hand
- `docs/` — 33 numbered Turkish documents, including `01-ARCHITECTURE`, `24-DECISION-LOG` (K-01…K-14), `27-OFFLINE-PROVISIONING`, `33-OPERATOR-CONSOLE`.
- `bfos/` — the device console (`operations.py` catalogue of 48 operations, `runner.py`, `tui.py`, `ui_gui.py`).
- `provisioning/offline-repo/` — the committed package pool and its manifests.
- No analytics, no customer logos, no testimonials, no benchmark numbers, and no screenshots of a working web panel exist. Nothing of the sort may be fabricated.
## Product Principles
1. **Fail closed, never half-install.** A device that cannot be verified stays unfinished and says so; a degraded success is worse than a clear refusal.
2. **Offline is a design property, not a deployment accident.** Anything the fleet needs travels with the fleet.
3. **Destructive work is explicit.** Approval and root are gates, not warnings.
4. **State is legible at a glance.** Which wave, which stage, which channel — readable in seconds, from a device screen or across 700 devices.
5. **Turkish for operators, English for the codebase.** No mixed-language surface.
## Accessibility & Inclusion
Field devices are operated in workshops and customer sites, sometimes on small or poorly lit screens; the console is used with a keyboard more often than a mouse. Readable contrast and non-colour-only status signalling are requirements, not enhancements. No formal standard (WCAG level) was agreed; none is claimed.

80
docs/DESIGN.md Normal file
View file

@ -0,0 +1,80 @@
# Blueforce Field OS — Design System
Two operator surfaces, one visual language: the **device console** (Python/tkinter,
ships on each field PC) and the **central panel** (Python stdlib `http.server`,
browser). Both describe the same fleet from opposite ends, so they share a status
vocabulary and a token set. Palette values live in `web/static/tokens.css`; the
console reads the same values through `bfos/ui_theme.py`.
## Direction contract
**THESIS.** The device's state is the subject, and it must be legible from a step
back — on a workshop laptop, in daylight, with someone waiting. This surface
refuses the category default it would otherwise inherit: a near-black "ops
console" with grey body text, six hues fighting for meaning, and status buried in
a log. Instead: a light ground, dark text with real contrast, and saturation
spent only where it carries a state.
**OWN-WORLD.** Light neutral ground (`--bg-app #f6f7f8`) with white raised panels
and hairline borders; one dark region, the app header, which anchors the frame.
One action accent (`--accent #1d4ed8`) marking the action the operator came to
take and the selected row, nothing else. Four state severities — ok / warn /
danger / info — each with a strong and a soft form, so a badge or a row can be
built from one palette without inventing a second. Data (ids, counts, exit codes,
timestamps) set in monospace so columns align; UI text in the platform face.
Buttons and inputs share one height; rows share one height; a 4px space rhythm.
**STORY.** The operator sees at a glance whether the device (or the fleet) is
ready, what stage it reached, and what the one next action is. A technician
understands an install's outcome without reading the log; an operator scanning
700 devices finds the ones that need attention without opening them.
**FIRST VIEWPORT.** Header band (dark, 56px) carrying the product name, the
target identity, and privilege level on the right — privilege is never a buried
sentence. Below it, a two-column body: left, the fleet/device list with a state
badge per row, sorted so non-ready devices surface first, with a filter bar and a
summary strip of counts by state (the one large figure per view). Right, the
selected device's detail: state, target id, hostname, wave, and the channel
verification list. The primary action sits in the header, not below the fold.
**FORM.** Operate surface. The world is a *control room board*: fixed regions,
legible readouts, one accent, state as signal. No decorative motion; the only
transition is a state change and a row selection.
**FINISH.** unreviewed and undocumented is unfinished; this build ends with the
finish review, the verdict, DESIGN.md, and every shipping raster carrying its
provenance.
## Status vocabulary
One word per state, Turkish, used identically in both surfaces.
| State | Token | Meaning |
|---|---|---|
| `KURULDU` | `--state-provisioned` | Provisioned offline, not yet enrolled |
| `KAYITLI` | `--state-enrolled` | Enrolled, channels not all verified |
| `HAZIR` | `--state-ready` | Every channel verified; the only shippable state |
| `HATA` | `--state-failed` | A check failed; the device needs a person |
| `YOK` | `--state-absent` | Not reachable / never seen |
Colour is never the only carrier: every badge ships its word, and the fleet table
sorts non-ready first, so the list is readable in greyscale.
## Privilege
Read-only and full privilege are visually distinct states, not a log line: the
header states the current level, and the console disables rather than hides
write operations when unprivileged, with the reason on the control.
## Rules
1. Saturation carries meaning. A colour is a state or the action accent; it is
never decoration, and a fifth hue is a bug.
2. Badges carry words. Greyscale must not lose the state.
3. One loud figure per view. Everything else is quiet and aligned.
4. No webfont. The fleet installs offline; the UI face is whatever the platform
ships and the data face is monospace.
5. Destructive actions look different and ask twice. Approval and the root gate
are design, not an afterthought.
6. The log is evidence, not the interface. Outcomes surface as state and
structure first; raw output stays available, collapsed.

119
web/static/tokens.css Normal file
View file

@ -0,0 +1,119 @@
/*
* Blueforce Field OS — design tokens
* ---------------------------------------------------------------------------
* One source for both operator surfaces:
* - the device console (Python/tkinter, bfos/ui_theme.py reads these values)
* - the central panel (stdlib http.server, web/templates/*)
*
* The surfaces share a status vocabulary because they describe the same fleet
* from two ends. A colour here means one thing everywhere: a device state, a
* severity, or an action weight. It never decorates.
*
* WHY LIGHT, NOT DARK
* The console is used in workshops, dealer service areas and customer sites --
* bright overhead light, daylight from a roller door, a 14" laptop panel. A
* near-black console loses its own body text there and its state colours
* collapse into equal-brightness smudges. So the ground is light, the text is
* dark, and saturation is spent only where it carries meaning: state.
*
* WHAT THE COLOURS HAVE TO CARRY
* The catalogue is 48 operations, 13 of which write. The state machine has
* three stages and a device can also be failed or absent. That is four
* severities plus one action colour, and no more -- a fifth hue would make the
* distinction something an operator has to read instead of see.
*/
:root {
/* -- Ground and structure -------------------------------------------- */
--bg-app: #f6f7f8; /* window ground */
--bg-panel: #ffffff; /* raised surfaces, cards, table body */
--bg-sunken: #eef0f2; /* wells: log output, code, inactive rows */
--bg-header: #1d2530; /* app header; the one dark region */
--border: #d5d9de; /* hairline between regions */
--border-strong: #b6bcc4; /* input borders, table rules */
/* -- Text ------------------------------------------------------------- */
--text: #1a1f26; /* body */
--text-muted: #5c6570; /* secondary lines, labels */
--text-faint: #8b939c; /* timestamps, hints, placeholders */
--text-on-dark: #e8ebee; /* header text */
--text-on-accent: #ffffff;
/* -- Action ----------------------------------------------------------- */
/* One accent. It marks the action the operator came to take and the
selection in a list; nothing else. */
--accent: #1d4ed8;
--accent-hover: #1a43bd;
--accent-active: #16389e;
--accent-soft: #e8eeff; /* selected row, focus ring fill */
--focus-ring: #1d4ed8;
/* -- State: the fleet vocabulary -------------------------------------- */
/* Four severities. Each has a strong form for text and a soft form for
fills, so a badge can be built from either without a second palette. */
--ok: #15803d;
--ok-soft: #e6f4ea;
--warn: #b45309;
--warn-soft: #fdf3e3;
--danger: #b91c1c;
--danger-soft: #fdecec;
--info: #1d4ed8;
--info-soft: #e8eeff;
--idle: #5c6570; /* not yet acted on: not a failure */
--idle-soft: #eef0f2;
/* -- Device state machine --------------------------------------------- */
/* PROVISIONED_OFFLINE -> ENROLLED -> READY, and the failure/absence that
sit outside the sequence. Kept as named tokens so no surface invents a
fifth stage or repaints a stage by accident. */
--state-provisioned: var(--info);
--state-enrolled: var(--warn);
--state-ready: var(--ok);
--state-failed: var(--danger);
--state-absent: var(--idle);
/* -- Type ------------------------------------------------------------- */
/* Workhorse faces only: this is an Operate surface, and the value of a
webfont here is negative -- the fleet installs offline from a committed
pool, so a font CDN is a broken page on every device. The UI face is
whatever the platform ships; the data face is monospace so device ids,
counts and exit codes line up in a column. */
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans",
"DejaVu Sans", sans-serif;
--font-data: ui-monospace, "Cascadia Mono", "JetBrains Mono", Menlo,
Consolas, "DejaVu Sans Mono", monospace;
--size-xs: 12px; /* timestamps, table meta */
--size-sm: 13px; /* labels, table body */
--size-md: 15px; /* body, inputs, buttons */
--size-lg: 18px; /* card titles */
--size-xl: 22px; /* view titles */
--size-2xl: 30px; /* the one headline figure per view */
--leading-tight: 1.25;
--leading-normal: 1.5;
/* -- Space ------------------------------------------------------------ */
/* A 4px rhythm. Named, so a component never hand-picks 7px. */
--sp-1: 4px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 24px;
--sp-6: 32px;
--sp-7: 48px;
--radius: 6px;
--radius-lg: 10px;
/* -- Elevation -------------------------------------------------------- */
/* Two levels. More would separate regions that are not separate. */
--shadow-sm: 0 1px 2px rgba(16, 24, 40, .06);
--shadow-md: 0 4px 12px rgba(16, 24, 40, .10);
/* -- Metrics ---------------------------------------------------------- */
--row-h: 44px; /* fleet table row: comfortable at arm's length */
--control-h: 36px; /* buttons and inputs share one height */
--header-h: 56px;
--sidebar-w: 220px;
}