Skip to content

0.1.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 13:22
· 31 commits to main since this release
Immutable release. Only release title and notes can be modified.
v0.1.0
5370aa3

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_VERSION is 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 through Content and 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_precision stopped 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 with expected 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.0 common.html section 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 /ui redirects onto it
    (#166). The bundle Trunk builds rides inside the ferrochart binary 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 /ui that names a file type the bundle does not
    hold answers 404, and any other path answers index.html, which is how a
    client-side route deep-links. Content-hashed assets are served
    public, max-age=31536000, immutable and index.html no-cache, each with
    its own media type and X-Content-Type-Options: nosniff. FERROCHART_UI=off
    drops the routes, and a binary built without the bundle serves no /ui
    route. With the quickstart compose.yaml the 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 of leptos,
    wasm-bindgen or web-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.sh refuses a control that draws its own
    focus ring or sets outline-none (#157). The stylesheet's base layer holds
    one :focus-visible rule 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.sh stands 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-shots walks 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.md page 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.
    A ui-e2e CI 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.sh refuses 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 renders control::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 serve proxies /api as 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. Its ApiError carries 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 own cdr_status and cdr_body through. A request that never reached
    an answer reports no status rather than an invented one.

  • The template library at /ui/templates lists 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 through unplaced and is shown beside the form.

  • A control for every one of the eighteen FieldKind variants (#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 the DV_PROPORTION invariants of openEHR RM Release-1.1.0
    data_types.html section 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/design draws 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}/compositions and
    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.md section 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 .opt operational 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.
    A FormValues is 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-form carries a round trip and a golden
    wire snapshot for it, the pair the form definition already had.

  • ValidationFailure can 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/design draws 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.sh and app/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 renderer job in CI: cargo fmt plus leptosfmt, 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-compose to ferrochart-form (#151), so the renderer can build
    the value it has to submit. openEHR RM Release-1.1.0 ehr.html section
    5.2.2 makes COMPOSITION.composer mandatory and nothing authorises a server
    to invent it, and the renderer links ferrochart-form alone. The code that
    turns one into a Reference Model CODE_PHRASE stays with the builder. The
    serialised shape is unchanged, and the surface tests prove it.

  • FormValues::remove_in is #[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-compose to ferrochart-form (#139). A renderer collects values
    and links ferrochart-form alone, 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.toml ignores two RustSec unmaintained advisories, RUSTSEC-2024-0436
    (paste) and RUSTSEC-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_in and FormValues::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_under forgets 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 --bins tried 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.