Files
mpabi 8bf8e22448
Check card layouts / check (push) Has been cancelled
feat: publish A1-A8 card viewer and FreeRTOS C generator
2026-07-19 16:43:35 +02:00

29 KiB
Raw Permalink Blame History

A1A8 Interactive Architecture Viewpoints

Status: design contract, version 0.2, 2026-07-18.

This document defines how STEM cards describe architecture, code and runtime evidence. It is a house convention, not a new UML standard. The convention is organized in the spirit of ISO/IEC/IEEE 42010:2022, uses UML 2.5.1 notation, adds selected MARTE 1.3 concepts for real-time systems, and uses PlantUML as the textual source of rendered diagrams.

Scope and rollout

A1A8 is runtime-independent. The fixed viewpoints are intended to support embedded RTOS software, ordinary C++ object models, POSIX or standard threads, and asynchronous Boost.Asio applications without changing the navigation grammar.

The rollout is deliberately staged:

  1. current application: FreeRTOS C++ cards on RV32I/Hazard3;
  2. later application: std::thread/std::jthread and Boost.Asio;
  3. not yet scheduled: concrete Asio/thread card repositories and lesson numbering.

The later profile is specified below so current diagrams do not bake in RTOS- only assumptions. It is a compatibility target, not a claim that the current FreeRTOS card already implements or teaches Asio.

Standards boundary

  • ISO/IEC/IEEE 42010:2022 supplies the concepts of architecture descriptions, viewpoints, views and model kinds. A1A8 is our concrete set of viewpoints. We do not claim full ISO conformance merely because a card uses these labels.
  • UML 2.5.1 supplies the notation for classes, objects, components, interactions, activities and state machines. It does not prescribe A1A8.
  • MARTE 1.3 may annotate schedulable resources, time, hardware and allocation in RTOS cards. A MARTE property must come from configuration, code or measurement; authors must not invent period, deadline or execution time merely to fill a box.
  • arc42 is a useful wider documentation template. A1A8 is the smaller, interactive teaching slice used inside a card, not a replacement for an architecture description of the whole product.
  • PlantUML is the text-first renderer. The .puml source, not a manually edited SVG, is authoritative.

Primary references:

The fixed navigation model

The tab names remain stable across cards. A subtitle identifies the actual mechanism being taught.

A1 CONTEXT
A2 STRUCTURE
A3 DISPATCH
A4 APPLICATION
A5 FLOW
A6 STATE
A7 RUNTIME
A8 PATTERNS

These eight tabs ask eight different questions:

ID Question Typical model kind
A1 CONTEXT Where is the subject and what lies outside its boundary? component, package or deployment view
A2 STRUCTURE What static elements and relationships construct it? class, component or object structure
A3 DISPATCH How is an abstract call bound to the actual implementation? class plus sequence/call-path view
A4 APPLICATION How does application code specialize or compose it? application class/object view
A5 FLOW What happens in what order? sequence or activity view
A6 STATE Which lifecycles and state transitions exist? state machine, never an unlabeled flowchart
A7 RUNTIME What actually exists in memory and in the debugger now? object diagram plus evidence overlay
A8 PATTERNS Which design roles and trade-offs explain the construction? participant map plus decision record

Not every card needs all eight views. The top-level navigation keeps all eight positions stable; an omitted view is visibly unavailable and states why. A small card should not create decorative diagrams to fill empty tabs.

Viewpoint contracts

A1 CONTEXT

Purpose: establish system boundary and dependency direction before class details appear.

Required content:

  • subject under study;
  • upstream application actor or component;
  • wrapped platform/API;
  • runtime platform, hardware or simulator when relevant;
  • one verb on every boundary-crossing relation.

For an RTOS wrapper this normally reads:

Application task
      │ specializes / uses
      ▼
C++ wrapper
      │ calls
      ▼
FreeRTOS C API
      │ schedules on
      ▼
Hazard3 simulator

A1 must not list every method. It is a map, not a class reference.

A2 STRUCTURE

Purpose: show the static construction that the compiler sees.

Required content:

  • class/interface/component names;
  • relevant fields and operations only;
  • visibility where it is part of the lesson;
  • precise relationship type: generalization, realization, composition, aggregation or dependency;
  • storage/ownership role when the structure owns a resource.

Class rows receive semantic anchors only when a card can connect them to a source line or a compile-time proof. Inheritance, friend, templates and visibility are CODE facts; they are never presented as runtime events.

A3 DISPATCH

Purpose: explain the binding mechanism between the generic boundary and the concrete implementation.

The title is fixed; the subtitle is mechanism-specific. Examples:

A3 DISPATCH · CRTP static dispatch
A3 DISPATCH · virtual dispatch through vtable
A3 DISPATCH · C callback and function pointer
A3 DISPATCH · event to subscriber
A3 DISPATCH · executor and completion handler

The diagram must identify:

  1. the call origin;
  2. the erased or abstract boundary;
  3. the cast, lookup or binding operation;
  4. the concrete target;
  5. whether the decision is compile-time or runtime;
  6. the ABI proof that distinguishes the mechanism.

This viewpoint prevents a wrapper from being mislabeled. A CRTP wrapper must show static_cast<Derived*> and the absence of virtual dispatch. A dynamic wrapper must show object address, vptr, vtable entry and indirect call.

A4 APPLICATION

Purpose: show one concrete use without repeating the complete library.

The generic mechanism is collapsed to its public contract. The view maps every pattern or framework role to the names used in the exercise, for example Strategy -> PaymentStrategy and Context -> ShoppingCart.

At least one application invariant is printed next to the model. Example: "ShoppingCart depends on the strategy interface, not a payment variant."

A5 FLOW

Purpose: show causal order for one named scenario.

Rules:

  • state the precondition and observable postcondition;
  • use sequence diagrams for collaborating participants and activity diagrams for algorithmic branching;
  • number messages in the same order as interactive steps;
  • show returns only when they carry teaching information;
  • split construction, scheduling and callback dispatch into phases when one page would hide the boundary.

A5 is the primary home of RUN steps and deterministic checkpoints.

A6 STATE

Purpose: separate lifecycle from execution flow.

When two state domains exist, draw two explicit lanes rather than merging them. For a FreeRTOS C++ task these are commonly:

C++ object lifetime:  absent -> constructed -> alive -> destroyed
Kernel task state:    Ready <-> Running <-> Blocked; Suspended; Deleted

Every transition has a trigger, guard or event. Values returned by platform APIs are platform states, not invented wrapper states. A wrapper-specific state machine is allowed only when the code really stores and enforces it.

A7 RUNTIME

Purpose: connect the model to a reproducible observation.

A7 is deliberately a hybrid, not pure UML. It combines an object diagram with selected debugger facts:

  • object and subobject addresses;
  • this, callback context and handles;
  • fields, ownership pointers, TCB and stack interval;
  • PC, SP and relevant argument registers;
  • vptr/vtable target for dynamic dispatch;
  • symbol/absence-of-symbol proof for static dispatch;
  • timestamp, target, ELF revision and checkpoint identity.

Every value is either captured from a deterministic run or marked as a symbolic example. A screenshot without target, source revision and timestamp is illustration, not evidence.

A8 PATTERNS

Purpose: explain why the design has its shape and what it costs.

Each claimed pattern records:

  • intent and problem;
  • forces/constraints;
  • canonical participant roles;
  • exact mapping from role to project class/function;
  • collaboration that makes the pattern real;
  • invariant or test;
  • benefit and cost;
  • rejected alternative;
  • conditions in which the pattern should not be used.

Pattern names are not decorative stereotypes. Facade is valid only when a simpler boundary to a subsystem can be identified. RAII is valid only when acquisition and release are safely paired for every legal lifetime. A callback bridge may be an Adapter without making the whole wrapper an Adapter.

Authored viewpoint record

The future machine-readable form should preserve this conceptual record. This is a design target, not yet a required card_source.json schema extension:

{
  "id": "a3-dispatch",
  "number": "A3",
  "label": "DISPATCH",
  "subtitle": "Dynamic polymorphism through vtable",
  "purpose": "Trace the C callback to the concrete C++ override.",
  "stakeholders": ["student", "teacher", "embedded developer"],
  "concerns": ["C-to-C++ bridge", "void* context", "vtable", "ABI"],
  "model_kind": "uml-sequence",
  "source": "doc/assets/a3-dispatch.puml",
  "fallback_text": "doc/assets/a3-dispatch.txt",
  "spread_group": "task-dispatch",
  "steps": [
    {
      "id": "dispatch-vptr",
      "number": 4,
      "mode": "RUN",
      "svg_target": "dispatch-vptr",
      "code_ref": "include/FreeRTOS/Task.hpp:110",
      "snapshot_ref": "task.dispatch.vptr",
      "evidence": "a0, object address, vptr, vtable entry and jalr target"
    }
  ]
}

Required semantics:

  • id, number and label are stable teaching identity;
  • subtitle names the actual mechanism and may differ between cards;
  • source is the text source of the rendered asset;
  • fallback_text is a compact readable map, not generated alt text;
  • code_ref is repository-relative and validated;
  • CODE steps may have no snapshot and never reset the program;
  • RUN steps require a deterministic replay target;
  • evidence says what proves the claim, not merely what should be visible;
  • spread_group joins logical continuations without changing A4 print pages.

Plain technical rendering profile

"Plain text UML" means two related things in this project:

  1. diagrams are authored as reviewable text;
  2. their rendered form resembles a restrained technical drawing rather than a presentation slide.

The default profile therefore uses:

  • white background, black/charcoal strokes and no shadows;
  • square or nearly square corners;
  • one monospaced family for identifiers, fields, methods and evidence;
  • one sans-serif family only for explanatory prose;
  • orthogonal relations where the renderer can keep labels legible;
  • left-aligned members and short, explicit verbs on relations;
  • no gradients, icons, ornamental colors or oversized titles;
  • line weight and spacing, not color, as the primary print hierarchy;
  • visible step anchors such as 0108 that remain useful in SVG and print;
  • two muted selectable tones in HTML and one subtle active outline;
  • non-selectable content in neutral gray, never confused with a selectable inactive element.

PlantUML can generate ASCII/Unicode text output for sequence diagrams. We may ship that output as the fallback for sequences. Other diagram kinds receive a small hand-maintained text map because PlantUML's ASCII renderer does not cover all UML kinds. The fallback communicates topology and reading order; SVG and TeX remain the visual outputs.

Orientation, type scale and page visibility

The default reading direction is vertical. A horizontal layout is reserved for a genuine sequence or timeline whose primary fact is execution order over time. A binding chain is vertical unless time or participant lifelines are being modelled explicitly.

Viewpoint Default orientation Reason
A1 CONTEXT vertical responsibility descends from application to platform
A2 STRUCTURE vertical base types, specializations and dependencies form tiers
A3 DISPATCH vertical binding stages form a top-to-bottom chain; use horizontal only for a genuine sequence
A4 APPLICATION vertical application objects lead to observers and invariants
A5 FLOW horizontal messages and time advance in reading order
A6 STATE vertical lifecycle transitions form explicit top-to-bottom paths
A7 RUNTIME geometry-dependent use vertical for an ownership descent, horizontal for a memory/CPU evidence pipeline
A8 PATTERNS geometry-dependent use vertical for tiers and horizontal for a short chain of roles and consequences

Use top to bottom direction unless the diagram is a genuine sequence or timeline (normally A5). A diagram must not be made horizontal merely to fill page width.

Physical page orientation follows the rendered model rather than the viewpoint name: use one portrait page, one landscape page, or an explicit semantic page composition. Prefer a single page after a deliberate layout change; introduce tiles only when readable labels and complete semantic blocks still cannot fit.

The default PlantUML type scale is 14 for ordinary labels, 15 for class names and 13 for class attributes. A smaller value is allowed only when the diagram remains readable at the normal A4 viewer scale. Prefer fewer words, shorter relation labels and a second logical page over shrinking text.

All diagrams belonging to one card use one shared PlantUML style include. The style is print-first and keeps the following invariants:

  • one monospaced family for nodes, relations and sequence messages;
  • a 190 px minimum class width, while content may make a node wider;
  • 1.5 px node borders and 1.3 px arrows;
  • a near-white node fill, blue-grey borders and dark text with sufficient monochrome-print contrast;
  • the same base styling for class, state and sequence diagrams;
  • no shadows, decorative gradients or per-diagram colour inventions.

Interactive selection is an overlay owned by the viewer. The source diagram must remain fully legible with no selection, while selectable nodes receive a clearer idle treatment than non-interactive context nodes. Selection changes emphasis, not geometry: it must not resize a node, move a label or alter the diagram layout.

At content scale = 100%, the HTML viewer preserves the intrinsic PlantUML scale. A diagram that exceeds the declared A4-safe viewport is split into semantic tiles; it is not silently compressed to width: 100%. The joined HTML spread reconstructs the logical page in a CSS grid, while ordinary view and print retain separate physical A4 sheets. Canvas zoom remains an explicit viewer action and may be used to inspect the joined overview.

Every diagram starts on a new A4 page in HTML and TeX. Its SVG must fit within the page-safe margins with all labels, arrowheads and numbered anchors visible. Long diagrams continue on a new page with stable global step numbers and may be joined by the HTML spread view. Every physical tile remains legible on its own, but its part, row and column identify its exact position in the logical model.

Large diagrams and page continuity

A viewpoint is one logical model even when it needs several A4 pages.

  • page_grid declares columns, rows and explicit semantic cuts_x/cuts_y;
  • a cut may cross whitespace or a connector, but never a class, object, state, participant, note, numbered anchor or other closed semantic block;
  • the generator validates the rendered SVG geometry and rejects a colliding seam instead of shrinking the diagram or accepting a broken print;
  • every interactive step belongs to exactly one tile through step_tiles;
  • all tiles use one logical figure number and one global navigation cursor;
  • each page carries A<n>, the same viewpoint title, part x/y, and a shared spread_group;
  • duplicated participants at a page boundary are marked continued, not treated as new objects;
  • step numbers remain global across parts;
  • connectors leaving a page use a named continuation token, for example D3 -> A5.2;
  • HTML can join the parts side by side; print keeps independent A4 pages;
  • the joined HTML view fits the complete logical row into the viewport, keeps page tops aligned and centers the semantic join rather than either sheet;
  • a screen-only continuation cue in every part header indicates available neighbours to the left, right, above and below;
  • selecting a step scrolls to the correct part and preserves one cursor;
  • no information is encoded only in the physical proximity of two pages.

The source diagram may use a modest, explicit PlantUML scale when that is necessary to place a semantic seam inside the A4-safe viewport. This is a design decision, not automatic fit-to-width. The resulting normal print must still meet the shared type-scale readability gate; HTML hover/canvas zoom is a convenience and never a substitute for readable print.

Interaction and correspondence rules

The diagram, source tree and debugger share one navigation identity:

SERIES -> CARD -> TASK -> BLOCK -> VIEWPOINT/PHASE -> STEP -> SNAPSHOT

For every selectable anchor:

  • its visible step number occurs exactly once inside that diagram;
  • click and keyboard navigation select the same step;
  • CODE opens the exact code or binary proof and does not mutate runtime;
  • RUN opens code and may replay only after explicit activation;
  • compile-time relations use code, nm, readelf or disassembly evidence;
  • runtime messages use checkpoint evidence;
  • focus is a subtle overlay; unselected selectable targets stay visible;
  • a missing snapshot_ref is valid for CODE and invalid for RUN;
  • the authored SVG target is validated after PlantUML rendering.

Diagram-first teaching contract

The diagram is the primary entry point into an example. A printed card must teach the algorithm, responsibility boundary, state transition or dispatch mechanism before it exposes implementation syntax. Complete source listings do not belong on the card. The card carries the repository URL/QR code, while an interactive CODE anchor opens only the relevant symbol or source range in Neovim.

The intended learning path is:

goal and scope
    -> relevant A1-A8 viewpoint
    -> numbered diagram element
    -> CODE source anchor or RUN checkpoint
    -> Neovim / Termdebug / Hazard3 evidence
    -> timestamped snapshot

Consequently:

  • a card uses only the A1-A8 viewpoints which materially explain the subject;
  • the diagram explains the mechanism, source confirms its implementation and the debugger supplies evidence;
  • CODE navigation does not restart or mutate the program;
  • RUN navigation may replay only to a declared deterministic checkpoint;
  • source excerpts may appear temporarily in the interactive Neovim view, but are not duplicated as full static listings in HTML, TeX or print;
  • every source jump is attached to a concrete diagram element, relation, message or state rather than to an unrelated code catalogue;
  • the repository link remains the canonical route to the complete source.

This order is deliberate: students first reason about algorithms, states and responsibilities, then inspect the C or C++ syntax that implements them.

Editable UML layout and JSON persistence

Interactive UML views may expose an explicit EDIT mode. It applies only to UML/model assets; it must not turn the whole card page into a free-form editor or interfere with normal CODE/RUN navigation.

PlantUML remains the authoritative semantic source for participants, relationships and diagram kind. Manual visual decisions are stored in a sidecar JSON document named after the source, for example:

doc/assets/a2-structure.puml
doc/assets/a2-structure.layout.json

Saving a layout persists the complete authored presentation state, not only a temporary browser transform. At minimum the sidecar records:

  • diagram identity, source revision and layout schema version;
  • canvas size, page orientation, scale and optional page-grid metadata;
  • stable element identity (id/svg_target), position and stacking order;
  • width, height, local scale and rotation when the model kind allows it;
  • displayed name, stereotype, short description and member text overrides;
  • font family, font size, weight, style, line height and text alignment;
  • fill, stroke, border width, corner treatment and semantic style role;
  • group membership, collapsed state and explicit grouping/ungrouping;
  • relation endpoints, labels and manually placed routing points;
  • numbered CODE/RUN anchor placement without changing its teaching identity;
  • editor metadata such as update timestamp and optional author.

A representative record is:

{
  "schema": "stem.uml-layout/v1",
  "diagram_id": "task01.a2-structure",
  "source": "a2-structure.puml",
  "source_digest": "sha256:...",
  "canvas": {
    "width": 1120,
    "height": 760,
    "orientation": "landscape",
    "scale": 1
  },
  "elements": {
    "task-base": {
      "x": 310,
      "y": 72,
      "width": 360,
      "height": 290,
      "z": 2,
      "text": {
        "title": "Task<Derived>",
        "description": "Static task wrapper",
        "font_family": "IBM Plex Mono",
        "font_size": 14,
        "font_weight": 500,
        "align": "left"
      },
      "style": {
        "role": "wrapper",
        "fill": "#eef5f8",
        "stroke": "#397c99",
        "stroke_width": 1.5
      }
    }
  },
  "relations": {
    "task-to-api": {
      "from": "task-base",
      "to": "freertos-api",
      "label": "calls",
      "points": [[490, 362], [490, 430], [690, 430]]
    }
  },
  "updated_at": "2026-07-19T12:00:00+02:00"
}

Editor requirements:

  • dragging, resizing, text editing, grouping and relation routing update an in-memory draft first;
  • Save layout validates stable IDs and writes the sidecar atomically;
  • Cancel restores the last saved sidecar without changing PlantUML;
  • Reset layout removes visual overrides and returns to the generated PlantUML geometry only after explicit confirmation;
  • rerendering PlantUML reapplies compatible overrides by stable ID and reports orphaned overrides instead of silently discarding them;
  • a source digest mismatch is visible to the editor but does not destroy the saved layout;
  • normal view and print consume the same saved JSON, so the authored geometry is reproducible outside the editing session;
  • CODE/RUN step IDs, source references and snapshot identities cannot be renamed accidentally by a visual edit.

Exact manual coordinates cannot be represented reliably through PlantUML layout hints alone. The sidecar is therefore the canonical manual presentation layer, while .puml remains the canonical semantic layer.

Two valid A3 profiles for FreeRTOS wrappers

The viewpoint set intentionally supports both of these designs.

Current teaching wrapper in K02

A2 STRUCTURE  Task<Derived>, SumTask, SupervisorTask
A3 DISPATCH   CRTP static dispatch
               void* -> Task<Derived>* -> Derived* -> run()
A7 RUNTIME    same object address; direct/inlined target; no vtable symbol

This matches the current K02 acceptance gate: no virtual dispatch, vtable, RTTI or hosted runtime.

jonenz/FreeRTOS-Cpp reference design

A2 STRUCTURE  TaskBase <- Task / StaticTask<N> <- application task
A3 DISPATCH   dynamic dispatch
               void* -> TaskBase* -> taskEntry() -> virtual taskFunction()
A7 RUNTIME    object address, vptr, vtable entry and indirect target

The upstream Task.hpp creates dynamic/static kernel tasks in constructors, passes this to callTaskFunction(void*), and uses a virtual taskFunction(). Its TaskBase destructor may call vTaskDelete(handle) when configured. These are reference facts, not automatic design choices for our teaching wrapper. Adopting that design would require changing K02's lifecycle, ABI and no-vtable acceptance contracts explicitly.

Reference: https://github.com/jonenz/FreeRTOS-Cpp/blob/develop/FreeRTOS-Cpp/include/FreeRTOS/Task.hpp

Target profile: Boost.Asio and standard threads

This is the required future compatibility profile. It does not rename the fixed tabs. Mechanism-specific wording belongs in subtitles:

A1 CONTEXT      · application, Asio, io_context, worker threads, OS
A2 STRUCTURE    · Server, Session, socket, strand and ThreadPool
A3 DISPATCH     · executor and completion-handler dispatch
A4 APPLICATION  · one concrete Session or Client
A5 FLOW         · asynchronous initiation and completion
A6 STATE        · operation, session and thread lifecycles
A7 RUNTIME      · threads, executors, handlers and owned buffers
A8 PATTERNS     · Proactor and verified collaboration roles

The fixed label remains A5 FLOW, not A5 ASYNC FLOW; the UI subtitle may be Async completion flow. Likewise the fixed label remains A7 RUNTIME, with Threads / executors as a subtitle. This keeps cards comparable while still naming the actual mechanism.

A1A8 mapping

A1 CONTEXT shows this dependency/execution stack:

Application Session / Server
        │ initiates
        ▼
Boost.Asio async operations, executors and strands
        │ queue completions in
        ▼
io_context
        │ run() is called by
        ▼
std::thread / std::jthread workers
        │ use
        ▼
operating-system sockets and scheduling

A2 STRUCTURE shows the static ownership and dependency graph: Server, Session, tcp::socket, executor/strand, buffers, io_context and the worker collection. It must make lifetime ownership explicit, especially when a session uses shared_from_this() to outlive an initiating call.

A3 DISPATCH traces an initiating function through the outstanding operation, associated executor, completion queue and selected handler. It must not draw async_read as if it created a user thread. Completion handlers are invoked only by threads currently running io_context::run(); several such threads form a pool from which io_context may choose. A strand adds strictly sequential handler invocation, not physical affinity to one thread.

A4 APPLICATION collapses Asio infrastructure and shows one concrete protocol/session contract, for example EchoSession::start/read/onRead/write.

A5 FLOW uses the explicit sequence:

initiate -> outstanding -> operation completes -> handler queued
         -> one run() thread invokes handler -> next operation or completion

A6 STATE separates at least three domains when present:

jthread object:       absent -> joinable -> stop requested -> joined
async operation:      initiated -> outstanding -> queued -> running -> done
application Session:  Created -> Reading -> Processing -> Writing -> Closed

The std::jthread destructor requests stop and joins when the object is still joinable. A session state is application data; it must not be inferred merely from a handler being present in a queue.

A7 RUNTIME records io_context, worker IDs, current handler and thread, executor/strand identity, socket descriptor, operation kind, session address, buffer lifetime and relevant smart-pointer ownership. The evidence view uses info threads, backtraces and object inspection rather than RTOS TCB fields.

A8 PATTERNS may identify Proactor, callback/continuation, serialized execution through a strand, thread pool, RAII thread ownership and lifetime management. These are classified carefully: std::jthread ownership is a C++ idiom, a thread pool is a concurrency architecture, and not every completion handler constitutes a GoF Command. Boost.Asio documents its asynchronous model as Proactor; the card must still map each claimed role to project code.

The central invariant for every future Asio card is:

asynchronous operation != thread

operation completion -> associated executor -> io_context
                     -> one thread currently executing run()

Primary references for this profile:

Definition of done for a card view

A viewpoint is ready when:

  1. its purpose and question are stated;
  2. the chosen UML/model kind fits that question;
  3. every relation has one unambiguous meaning;
  4. every selectable item has a stable anchor and code reference;
  5. runtime claims have reproducible evidence;
  6. compile-time claims use compiler/binary evidence rather than fake runtime;
  7. the text fallback remains understandable without SVG;
  8. the view prints in monochrome and works as an interactive HTML asset;
  9. continuation across pages preserves identity and step order;
  10. A8 names costs and rejected alternatives, not only benefits.