79 lines
4 KiB
Markdown
79 lines
4 KiB
Markdown
# Web Dashboard layout V1
|
|
|
|
Status: `WEB_DASHBOARD_LAYOUT_V1=PASS/FROZEN`
|
|
|
|
## File contract
|
|
|
|
The canonical UI preference is
|
|
`$XDG_CONFIG_HOME/trainlog/web/dashboard-layout-v1.json`, falling back to
|
|
`$HOME/.config/trainlog/web/dashboard-layout-v1.json`. Trainlog-owned
|
|
directories are mode 0700 and the regular file is mode 0600. It is not SQLite,
|
|
training data, an exchange format or synchronization input.
|
|
|
|
The strict document has exactly `format`, `version`, `revision`, `columns` and
|
|
`tiles`. Format is `trainlog-dashboard-layout`, version is 1, columns is 12,
|
|
and all seven stable Grid V1 IDs occur exactly once. Tiles contain only
|
|
`id,x,y,width,height`; responsive projections and translated titles never
|
|
persist. The canonical constraint/default table is
|
|
`contracts/dashboard-layout-v1.json`; TypeScript consumes it directly and a
|
|
Meson contract test rejects C divergence.
|
|
|
|
Revision zero denotes no valid persisted preference. A successful PUT stores
|
|
the expected revision plus one. The maximum is JavaScript's safe integer,
|
|
9,007,199,254,740,991. Coordinates are integral, `y <= 200`, dimensions obey
|
|
the frozen per-tile bounds, all tiles fit 12 columns and overlap is forbidden.
|
|
|
|
## Parser and boundedness
|
|
|
|
Trainlog uses system `yyjson` 0.13.0 (MIT), selected for strict typed C parsing,
|
|
small surface and pkg-config/Meson integration. HTTP bodies and files are both
|
|
limited to 4 KiB. Only the exact four-level schema is accepted; unexpected
|
|
keys, types, array sizes and unsupported versions are rejected. IDs fit a
|
|
32-byte bounded buffer and the tile count is exactly seven. No general JSON
|
|
framework was introduced.
|
|
|
|
Reads use `O_NOFOLLOW|O_CLOEXEC|O_NONBLOCK`, `fstat`, regular-file and size
|
|
checks. A missing file is normal. An invalid, oversized, non-regular or symlink
|
|
preference yields the complete default with source `invalid_persisted`; it is
|
|
not silently removed or replaced, and the server emits one bounded diagnostic.
|
|
|
|
## Atomic mutation
|
|
|
|
PUT validates before filesystem mutation, creates a same-directory `mkstemp`,
|
|
sets 0600 and close-on-exec, writes fully, fsyncs, closes, atomically renames,
|
|
then attempts directory fsync. Failures before rename unlink the temporary and
|
|
leave the prior file untouched. After rename the visible commit is authoritative
|
|
even if directory fsync cannot prove crash durability, so clients are never
|
|
invited to retry against a stale revision.
|
|
|
|
DELETE checks the same revision then unlinks only the preference and attempts
|
|
directory fsync. It never touches SQLite. Saving the default through PUT is
|
|
valid and deliberate; frontend Reset remains a draft until Save.
|
|
|
|
## HTTP and mutation security
|
|
|
|
`GET /api/v1/dashboard-layout` returns default, persisted or invalid-persisted
|
|
source, an ETag containing the revision and an ephemeral CSRF token header.
|
|
`PUT` and `DELETE` require the loaded ETag in `If-Match`; stale revisions return
|
|
412. Missing/malformed preconditions return 428.
|
|
|
|
Mutations additionally require an exact Origin of either the direct active
|
|
loopback URL or `http://trainlog.perf`, plus the 256-bit startup-random token in
|
|
`X-Trainlog-CSRF-Token`. The token is never persisted, logged or placed in a
|
|
URL. nginx may keep rewriting backend Host to the already accepted
|
|
`127.0.0.1:8080`; Origin remains the browser origin and requires no proxy
|
|
change. Host validation, loopback binding, no-CORS behavior and request bounds
|
|
remain unchanged.
|
|
|
|
## Frontend lifecycle
|
|
|
|
The Dashboard withholds the grid behind a discreet loading state until GET
|
|
finishes, avoiding default-to-persisted flashing. Network failure falls back to
|
|
the default with an accessible warning and disables Save because no safe
|
|
revision/token was loaded. Entry into edit mode snapshots the loaded canon;
|
|
Cancel is local, Reset changes only the draft, and Save validates then PUTs.
|
|
Success adopts the server response and revision. A 412 keeps editing open,
|
|
announces the conflict and exposes `Recharger l'agencement`.
|
|
|
|
Only the 12-column desktop canon is sent. Six-column/tablet and one-column/phone
|
|
projections remain derived and cannot trigger mutation.
|