Writing messages (LMF1)

Load this card when a task adds or changes a StringResources template, a notification, a FormattedText value, a data name or a formatted chart label, or translates resources. Studio checks the catalog's keys and the FormattedText host, but it does not parse LMF1 templates or their arguments: this card is the only place a CodeBot meets those rules. They are generated from the Libertas Message Format and the App contract, which own them. The formatted output card carries the presentation rules for where a FormattedText node may appear.

Order of work

  1. Declare every key in the one package catalog (lmf.resources.*).
  2. Write each template with zero-based positional arguments and MF1 syntax (lmf.syntax.*, lmf.styles.*, lmf.libertas-enum.*, lmf.types.*).
  3. Encode each argument with its most specific type and pass the canonical bytes (lmf.arguments.*, lmf.currency.*, lmf.temporal.*, lmf.tuple.*, lmf.formatted-text.*).
  4. Apply the rules of each use (lmf.notifications.*, lmf.charts.*, lmf.data-names.*) and preserve the structure in translation (lmf.translation.*).
  5. Review every template and locale (lmf.review.*).

Rules to apply

Studio does not check these rules. Each is quoted from its owner.

Documentation and localization

Owner: app-contract.md#documentation-and-localization

  • lmf.resources.stable-keys: String-resource keys are stable identifiers and are never translated. Values are translated as LMF1, preserving the structure the message-format contract defines.

Libertas Message Format

Owner: message-format.md#libertas-message-format

  • lmf.tuple.identity: Preserve the tuple; never replace its identity with pre-rendered text.

Uses and storage

Owner: message-format.md#uses-and-storage

  • lmf.formatted-text.source-markers: Use @LibertasFormattedText in TypeScript or #[libertas_formatted_text] in Rust, without arguments. See the TypeScript marker and Rust marker.
  • lmf.formatted-text.canonical-tuple: The Bytes payload is always the canonical tuple, even when it contains only a resource key and an empty argument list. Payloads never contain templates.
  • lmf.formatted-text.no-base64-string: A generic JSON boundary may represent those bytes as Base64 as part of its normal Bytes codec, but that transport spelling is not an App-authored String value and must not be copied into one.
  • lmf.formatted-text.pass-bytes: Pass those bytes directly; do not Base64-wrap them in App code. App code ordinarily does not decode formatted text: a client resolves and renders it at the final presentation boundary.

LMF1 argument syntax

Owner: message-format.md#lmf1-argument-syntax

  • lmf.syntax.zero-based: Every argument identifier is a zero-based position. Named arguments and one-based printf numbering are not LMF1.
  • lmf.syntax.no-printf: For a resource that displays one literal-text argument, use "{0}", not "%1$s" or "%s". Printf placeholders are ordinary text in LMF1 and appear unchanged on screen. The same syntax applies to every locale's StringResources, including translated resources. A LiteralText argument is inserted as text; braces or percent signs inside it are not parsed again.
  • lmf.syntax.mf1-features: LMF1 retains MF1 apostrophe quoting, plural offset:, exact plural selectors such as =0, required other branches, and # substitution inside plural variants.
  • lmf.translation.preserve-structure: Translators may reorder positional arguments and translate literal text inside variants. They must preserve argument positions, types, styles, selector keys, plural offsets, braces, and apostrophe escaping.

Libertas extensions

Owner: message-format.md#libertas-extensions

  • lmf.libertas-enum.style: LibertasEnum carries an integer value. Its style is required and is the actual Enumeration type name used to resolve the localized variant. Other Libertas object types, including LibertasDevice and LibertasUser, resolve through the client's ordinary permission-aware object resolver.
  • lmf.types.encoded-wins: The encoded Libertas Message Argument type has priority over a conflicting template type. This overlap with MF1 is intentional: message data remains self-describing even when a translation is stale or wrong. For example, an encoded device ID must still resolve as a device when a template mistakenly requests number; an encoded physical-unit value keeps its unit while the LMF1 number style controls numeric presentation.
  • lmf.types.compatible-template: The template type supplies semantics only when compatible with the encoded argument. LibertasEnum may refine an integer-backed enum argument with the Enumeration name in style; it must not reinterpret an argument encoded as a device, literal text, resource text, or physical unit.

Number and date/time styles

Owner: message-format.md#number-and-datetime-styles

  • lmf.styles.authoritative: The MF1 style on number, date, and time is authoritative presentation metadata. There is no parallel Libertas syntax for specifying a message's number or date format. In particular, schema Format attributes describe ordinary scalar fields and do not override an LMF1 argument style.
  • lmf.styles.unit-arguments: Unit-qualified encoded arguments use the same number style while retaining their encoded physical unit and client-side conversion policy. Do not append a hard-coded unit in the template.

Libertas Message Arguments

Owner: message-format.md#libertas-message-arguments

  • lmf.arguments.specific-type: Use the most specific semantic type available. Encoding a LibertasDevice ID takes the same payload space as encoding a plain integer of the same width; developers must encode it as the device ID rather than erase its meaning.
  • lmf.arguments.nesting-limits: Clients accept at most 16 nested FormattedText levels and 4,096 encoded bytes per tuple.
  • lmf.currency.code-and-scale: The code is three uppercase ASCII letters, and the scale is 0 through 6, matching schema Money.
  • lmf.currency.pattern-restrictions: Currency patterns cannot use percent, per-mille, scientific notation, or currency-sign sequences wider than three. Numeric styles without an unquoted currency sign, including integer, percent and 0.00, retain the default ISO-code currency presentation. A conflicting template type ignores its style. Neither a style nor the locale may replace the encoded currency or scale. The renderer must not round through a floating-point value. The encoded currency semantics take precedence over a conflicting template type.
  • lmf.currency.not-selector: Currency is not a plural or select operand; use a separate count argument when selection is needed.

Temporal arguments

Owner: message-format.md#temporal-arguments

  • lmf.temporal.dateonly-range:

    Argument Tag Avro payload Meaning and accepted values
    DateOnly 15 Long Valid Gregorian calendar date in yyyyMMdd form, years 1000 through 9999
  • lmf.temporal.datetime-range:

    Argument Tag Avro payload Meaning and accepted values
    DateTime 16 Long Unsigned Unix epoch seconds, from 0 through 253402214399
  • lmf.temporal.timeonly-range:

    Argument Tag Avro payload Meaning and accepted values
    TimeOnly 17 Long Seconds within the civil day, from 0 through 86400 inclusive
  • lmf.temporal.datetime-seconds: DateTime uses the SDK's existing unsigned-integer encoding in the Long payload; its accepted range fits a positive signed Long. The payload is seconds, not the millisecond convention used by some native formatting libraries.

  • lmf.temporal.combined-instant (judgment): For a combined instant, compose its date and time components in the same template, for example {0,date,long} at {0,time,short}. Both refer to the same encoded instant and rendering timezone; the localized template owns the connecting words. Long and full DateTime time styles include the timezone; TimeOnly has no timezone name at any width. Short time omits seconds and the other widths include them, using the locale's clock convention.

  • lmf.temporal.not-selector: Temporal arguments cannot serve as numeric plural, choice, or select operands. Reject invalid dates or out-of-range temporal payloads even when the template does not display the argument.

  • lmf.temporal.not-physical-unit: PhysicalUnit arguments represent measured quantities, including durations in CLDR units. They do not establish a calendar date, an epoch origin, or a civil clock time; use these temporal argument types for those values.

Notifications

Owner: message-format.md#notifications

  • lmf.notifications.key-and-values: Send a stable resource key and typed values. Recipients, importance, and source are delivery metadata rather than LMF1 arguments:

    Rust
    libertas_notification_send(
        &[recipient],
        NotificationImportance::Info,
        Some(room),
        "ROOM_STATUS",
        &arguments,
    );
    
  • lmf.notifications.literal-diagnostics (judgment): Use literal notifications only for genuinely non-localizable diagnostics. Do not put sensitive identifiers into literal text when a typed object argument provides permission-aware presentation.

Runtime fields with FormattedText=true

Owner: message-format.md#runtime-fields-with-formattedtexttrue

  • lmf.formatted-text.structured-authoritative (judgment): Structured fields remain authoritative for automation, validation, controls, and chart geometry. Formatted fields follow the static read-only placement and request-value contract:

    Rust
    //! Room condition messages
    //! Gives occupants localized summaries of current room conditions.
    //! #[libertas_string_resources(PROTOCOL_STRINGS)]
    
    pub const PROTOCOL_STRINGS: &[(&str, &str)] = &[
        ("READY", "Room condition reporting is ready."),
        ("ROOM_STATUS", "{0,LibertasDevice} reports a room temperature of {1,number,0.0}."),
    ];
    
    /// Room condition
    /// Describes the latest reported condition in one room.
    #[derive(LibertasExport, LibertasAvroDecode, LibertasAvroEncode)]
    pub struct RoomState {
        /// Availability summary
        /// Tells occupants whether the room condition report is ready.
        #[libertas_read_only]
        #[libertas_formatted_text]
        pub summary: Vec<u8>,
    
        /// Current room condition
        /// Tells occupants what is happening in the room and includes its relevant measurements.
        #[libertas_read_only]
        #[libertas_formatted_text]
        pub status: Vec<u8>,
    }
    
    fn encode_room_state(room: LibertasDevice, temperature_celsius: f64) -> RoomState {
        let status_arguments = [
            LibertasMessageArgument::Object(room),
            LibertasMessageArgument::UnitDouble {
                unit_type: "temperature-celsius",
                value: temperature_celsius,
            },
        ];
        RoomState {
            summary: libertas_formatted_text("READY", &[]),
            status: libertas_formatted_text("ROOM_STATUS", &status_arguments),
        }
    }
    

Charting

Owner: message-format.md#charting

  • lmf.charts.blob-identity: LMF1 affects only the label. The formatted Bytes blob remains the raw chart domain, grouping, ordering, shared-scale, and interaction identity. Two tuples that render to the same text remain distinct categories. A locale or template change may alter measurement and therefore guide layout, but must never alter chart geometry derived from data, domain membership, or category identity.

Data and database names

Owner: message-format.md#data-and-database-names

  • lmf.data-names.translation-keeps-keys: Translation must not change stored keys or values read by business logic.
  • lmf.data-names.not-storage-keys: Such names are formatted payloads, not storage keys; the KV API uses only the declared binary key for record identity.

Failure handling and review checklist

Owner: message-format.md#failure-handling-and-review-checklist

  • lmf.review.declare-keys: declare every notification, data-name, permission-reason, and formatted-output key in the package StringResources catalog;
  • lmf.review.stable-keys-templates (judgment): use stable resource keys and realistic default-locale LMF1 templates;
  • lmf.review.verify-locales (judgment): use zero-based positions and verify every locale against the same typed argument list;
  • lmf.review.trusted-templates: keep templates trusted—never treat user input as an LMF1 template; and
  • lmf.review.test-cases (judgment): test reordered arguments, apostrophe escaping, number/date styles, plural/select/selectordinal/choice branches, missing resources, unresolved Libertas values, and malformed formatted-text bytes.

Rules Studio enforces

Studio rejects a source that breaks one of these rules. The owner holds the full rule.

Uses and storage

Owner: message-format.md#uses-and-storage

  • lmf.formatted-text.bytes-host: FormattedText=true requires a Bytes host
  • lmf.resources.one-catalog: One App-package StringResources catalog owns stable schema keys and localized templates; functions and types carry no resource catalogs
  • lmf.resources.key-sets-match: Schema and default-document StringResources key sets match exactly; duplicate, missing or undeclared keys are errors

Search Libertas