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:
parent
d6d22c6e56
commit
4ad20452e7
4 changed files with 271 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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
69
PRODUCT.md
Normal 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
80
docs/DESIGN.md
Normal 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
119
web/static/tokens.css
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue