Repository navigation
0.1.0
Added
- "Write your own renderer" in the book, which is the page that makes the
published form definition a contract rather than an internal type (#26). It
describes the three documents and their version, the externally tagged
shape, why a key carries five parts, how an entered value is addressed
inside a repeating group, where a refusal is drawn and where it is not, and
which two changes are deliberately not a version bump.
Changed
- The published documents are externally tagged, and
FORMAT_VERSIONis 2
(#154). A variant is spelled{"quantity": {…}}where version 1 wrote
{"kind": "quantity", …}. The reason is measured rather than aesthetic: an
internally or adjacently tagged enum cannot be deserialised in one pass, so
serde buffers the input throughContentand monomorphises the whole subtree
twice. Over the renderer that cost 33269 gzipped bytes, 7.6% of the bundle
a reader downloads, confirmed by building both ways. The same buffer is
what #103 broke against, when a dependency enabling
serde_json/arbitrary_precisionstopped these documents deserialising with
no line changed in this tree. An externally tagged enum never enters that
code path, so the change removes a hazard class as well as the bytes.
Fixed
- A refusal from the composition builder is drawn on the repeat it came from
(#152). A validation failure carried no occurrence, so a form with a
repeating group showed "this value is out of range" under every repeat of
the field rather than the one that was wrong, and 67 of the 121 forms the
committed pack derives to carry a repeating group. The builder walks a form
with the occurrence of every repeating group above each field, so it knows
the address and now states it. - A refusal from the operational template still states no occurrence and is
still drawn on every repeat. Its Reference Model path carries positional
predicates into attribute arrays, and nothing establishes their
correspondence to a form's occurrence path, so an address there would
sometimes be invented. The type says which judgements know and which do not.
Fixed
- A COMPOSITION built around a template rooted below COMPOSITION states its
template identifier where the template is active, and a real CDR now accepts
it (#163). It was written at the wrapper COMPOSITION, a document FerroCHART
supplies from configuration and where no template is active, so FerroEHR
4.1.1 refused 66 of 66 such templates withexpected RM type conforming to OBSERVATION but found COMPOSITION. Every one had passed FerroCHART's own
gate first, which is the failure this project is least allowed to have.
openEHR RM Release-1.1.0common.htmlsection 3.2.3 makes the identifier
conditional: it is stated "if a template was active at this point in the
structure". - The read-back looks for the template identifier wherever it legitimately
sits, rather than at the document root alone, so the guard that refuses a
composition from another template keeps working for the 113 templates whose
identifier moved.
Changed
- The tied-sibling fold cites ADL 1.4 section 5.3.4.2 rule VCOC rather than
AOM 2's VSONCO (#142). Four places in this repository said VSONCO was "the
only definition of collective sibling occurrences openEHR publishes; AOM 1.4
defines none", and that is false: ADL 1.4 publishes VCOC, in the generation
these templates are written in, and it states the sum of upper bounds, which
is the bound that was in dispute. The arithmetic is unchanged, because it
was already what VCOC requires. The upstream-report register loses a
consequence it should never have carried.
Added
- The server serves the renderer at
/ui/, and/uiredirects onto it
(#166). The bundle Trunk builds rides inside theferrochartbinary as a
table of files the build script writes, so a release archive and the
container image behave the same and a request path never reaches the
filesystem. A path under/uithat names a file type the bundle does not
hold answers404, and any other path answersindex.html, which is how a
client-side route deep-links. Content-hashed assets are served
public, max-age=31536000, immutableandindex.htmlno-cache, each with
its own media type andX-Content-Type-Options: nosniff.FERROCHART_UI=off
drops the routes, and a binary built without the bundle serves no/ui
route. With the quickstartcompose.yamlthe address is
http://127.0.0.1:8080/ui/. - Every release carries a second CycloneDX document,
ferrochart-renderer-<tag>-<target>.cdx.json, attested against the same
archive (#166). The server's own document reaches none ofleptos,
wasm-bindgenorweb-sys, because the cargo feature that compiles the
bundle in is empty and there is no dependency edge to follow, so the
WebAssembly the binary serves to every reader was absent from the document
that claims to describe what shipped. The lane refuses to publish a renderer
document that lists none of those three. scripts/checks/palette-utilities.shrefuses a control that draws its own
focus ring or setsoutline-none(#157). The stylesheet's base layer holds
one:focus-visiblerule for the whole application, and that rule is what
guarantees every control has an indicator; a class that overrides it removes
one silently. The assertion existed in one kit module of eleven and now
covers every source file, including the ones nobody has written yet.
Added
- The book shows the renderer, and a test takes the pictures (#93).
scripts/ui-e2e.shstands up the form surface over two committed CKM
templates, serves the renderer bundle against it, and drives a pinned
headless Chromium through the template library, both forms and the design
system over WebDriver.--docs-shotswalks the same screens again on both
grounds and writes one PNG each into
website/book/src/operate/img/renderer, which the new
website/book/src/operate/renderer.mdpage and the book's introduction
embed. The journeys and the capture share one definition of what a screen
must have drawn (e2e/tests/it/screens.rs), so a screenshot cannot outlive
the journey that proves it, and an ordinary run rewrites no tracked file.
Aui-e2eCI job runs the battery on every pull request, uploads what a
failed journey was looking at, and fails when a run changed a tracked file.
scripts/checks/docs-shots.shrefuses an image that is not a PNG, is small
enough to be a blank page, carries a name the capture does not write, or is
committed with no page embedding it. - The form screen draws the controls (#26).
/ui/forms/{template_id}fetched
a definition and rendered its group tree as headings and labels, because the
request module and the controls were built in parallel and nothing joined
them. It now renderscontrol::group::FormBody, so a clinician sees the
form rather than its outline: inputs, the null-flavour affordance beside
each field, a repeatable group carrying its occurrence bounds, and a field
the template fixed saying so instead of offering an entry. trunk serveproxies/apias well as/health, so a local session runs
the whole surface rather than half of it.
Added
-
The renderer's conversation with the server (#138). One module,
app/ferrochart-renderer/src/api, owns every request the browser makes over
gloo-net, and a test scans the crate's own sources and fails when any other
module names the transport. ItsApiErrorcarries the status the server
answered with and the bytes it answered with, reads the surface's error
document so the stable code and the message arrive as fields, and passes a
CDR's owncdr_statusandcdr_bodythrough. A request that never reached
an answer reports no status rather than an invented one. -
The template library at
/ui/templateslists what the server holds, and
/ui/forms/{template_id}draws the group tree of one form definition as
headings with the field labels beneath. A read renders its failure inline,
with the status, the server's code, the CDR's answer and every link of the
cause chain, and never as a toast. -
app/ferrochart-renderer/src/placement.rs, the lookup a control calls to
find the validation failures that belong to it. A failure lands on every
control its node key names, and one about how many nodes there are is drawn
once rather than under every repeat. A failure that names no field of the
form stays reachable throughunplacedand is shown beside the form. -
A control for every one of the eighteen
FieldKindvariants (#137), each
rendering from a pure admission function that refuses what the template
refuses: a boolean whose two flags are independent, a text field whose
closed list is the whole permitted set, a coded field that says plainly when
a value set needs a terminology server rather than inventing an expansion,
an ordinal that stores the symbol and shows the rubric, a quantity whose
magnitude range and decimal precision follow the chosen unit, a proportion
that holds theDV_PROPORTIONinvariants of openEHR RM Release-1.1.0
data_types.htmlsection 6.2.10, and a date, time and date-time at the
precision the template states and no finer. -
The null-flavour affordance beside a field, and the add and remove pair a
repeatable field or group carries, disabled at the template's ceiling and
floor. Content the template left undetermined is drawn as a visible hole
carrying its reason and captures nothing. -
Ten design-system affordances beside the buttons and surfaces already there:
the table shell, tab pills, the segmented control, badges and status pills,
the stat tile, the empty state, the loading skeleton, the confirm dialog,
the toast queue, and the inline notice family. Each has one definition, and
the living style guide at/ui/designdraws every one of them beside a
synthetic form carrying one field of every kind. -
The HTTP form surface (#146):
GET /api/templates,
GET /api/templates/{template_id}/definition,
POST /api/templates/{template_id}/validation,
POST /api/ehrs/{ehr_id}/templates/{template_id}/compositionsand
GET /api/ehrs/{ehr_id}/compositions/{uid}/values?template={template_id}.
No specification governs a route of it: openEHR ITS-REST defines the CDR's
API and says nothing about a form server in front of one, and
docs/architecture.mdsection 11.1 is the design of record. An unknown
template is 404, a body the route cannot read is 400, values the template
refuses are 422 with the report, and a CDR that refused or never answered is
502 carrying the CDR's own status and body. -
FERROCHART_TEMPLATES, a directory of.optoperational templates the
server compiles at startup into a form definition and a flattened validator
per template. A template that will not compile fails the startup naming the
file, and so do two files stating one template identifier. The variable is
optional, and unset means the server holds no template. -
The entered-value types (
FormValues,Slot,Entered,Datum) serialise.
AFormValuesis a JSON array of entries, each spelling out the node key,
the occurrence path and the occurrence beside the value, so the occurrence
path survives the wire.ferrochart-formcarries a round trip and a golden
wire snapshot for it, the pair the form definition already had. -
ValidationFailurecan name the composition builder as its source, so a
required field left empty reaches a renderer keyed onto that field rather
than as prose it cannot place. -
The design system (#101): the renderer is a Leptos client-side binary built
by Trunk, and it lands with its frame before its content. Every screen
styles against semantic tokens, dark mode is the same token names redefined
under.dark, and the accent is the Rose & Iron palette
(assets/brand/README.md). The shell is three columns, a 56px topbar over a
208px rail, a canvas, and a persistent inspector, with the rail becoming a
focus-trapped overlay below the breakpoint. A living style guide at
/ui/designdraws every affordance the kit defines. -
The token contrast gate: the stylesheet is the one place a colour is
written, and the renderer's tests parse it and re-measure every pair the
design depends on, in both themes, against WCAG 2.2 success criteria 1.4.3
and 1.4.11. Three values differ from the sibling viewer as a result: the
dark accent is blush rather than rose-light, a control boundary is darkened
until it clears 3:1, and a placeholder takes the muted step rather than the
faint one. -
scripts/checks/palette-utilities.sh: a raw palette utility, a per-element
dark:override, or a hex colour outside the stylesheet fails the build.
Neither sibling enforces this rule, and both state it in prose. -
scripts/checks/bundle-size.shandapp/ferrochart-renderer/bundle-size.json:
a ceiling and a per-change budget per asset. A pull request is charged
against the baseline recorded in the merge base rather than in the branch,
so a change cannot pass itself by editing the number. -
The
rendererjob in CI:cargo fmtplusleptosfmt, clippy on
wasm32-unknown-unknown, the tests, the palette check, a release bundle
build, and the bundle budget.
Changed
-
The envelope (
Envelope,Composer,Subject,Setting) moved from
ferrochart-composetoferrochart-form(#151), so the renderer can build
the value it has to submit. openEHR RM Release-1.1.0ehr.htmlsection
5.2.2 makesCOMPOSITION.composermandatory and nothing authorises a server
to invent it, and the renderer linksferrochart-formalone. The code that
turns one into a Reference ModelCODE_PHRASEstays with the builder. The
serialised shape is unchanged, and the surface tests prove it. -
FormValues::remove_inis#[must_use]. A caller that discards what was
forgotten now says so, and the attribute caught two sites that did not. -
The entered-value types (
FormValues,Slot,Entered,Datum) moved from
ferrochart-composetoferrochart-form(#139). A renderer collects values
and linksferrochart-formalone, so leaving them behind the composition
builder left the browser unable to name the type it exists to fill in. The
types carried no serialisation at the time of the move, so nothing on any
wire changed with it. -
deny.tomlignores two RustSec unmaintained advisories,RUSTSEC-2024-0436
(paste) andRUSTSEC-2026-0173(proc-macro-error2). Both crates are
build-time proc-macro dependencies of Leptos, neither is a vulnerability,
and no code from either reaches the bundle a reader downloads. Tracked as
#135. -
FormValues::remove_inandFormValues::remove_under: a set of entered
values could be written to and never unset, so a form had no way to say a
clinician removed an occurrence.remove_underforgets everything inside
one instance of one group, which is what removing a repeat means.
Fixed
-
The release lane builds and describes the binary it ships rather than every
binary the workspace declares (#166).cargo auditable build --workspace --binstried to link the renderer, which is a browser binary, for
x86_64-unknown-linux-musl, the Package step would have tarred it into the
archive, and the SBOM guard refused the whole lane because the workspace
declares a bin set it did not describe. The lane now builds-p ferrochart
with the renderer feature on, packages the released package's own bins, and
the guard names both bins and says which document covers which. -
A repeatable group builds one instance per occurrence a clinician entered,
and reads back into the instance it came from (#141). The composition
builder used to build exactly one instance of every group whatever the
template admitted, so a second entry in a repeating group was dropped
without a word; 67 of the 121 forms the committed pack derives to carry at
least one repeating group, and 1868 fields sit under one. A value is now
addressed by the occurrence of every repeating group above it as well as by
its field and its own occurrence, the builder appends an index at each such
group, and the read-back assigns the same indices in document order.