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
- Declare every key in the one package catalog (
lmf.resources.*). - Write each template with zero-based positional arguments and MF1 syntax
(
lmf.syntax.*,lmf.styles.*,lmf.libertas-enum.*,lmf.types.*). - Encode each argument with its most specific type and pass the canonical
bytes (
lmf.arguments.*,lmf.currency.*,lmf.temporal.*,lmf.tuple.*,lmf.formatted-text.*). - Apply the rules of each use (
lmf.notifications.*,lmf.charts.*,lmf.data-names.*) and preserve the structure in translation (lmf.translation.*). - 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@LibertasFormattedTextin 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'sStringResources, including translated resources. ALiteralTextargument is inserted as text; braces or percent signs inside it are not parsed again.lmf.syntax.mf1-features: LMF1 retains MF1 apostrophe quoting, pluraloffset:, exact plural selectors such as=0, requiredotherbranches, 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:LibertasEnumcarries an integer value. Itsstyleis required and is the actual Enumeration type name used to resolve the localized variant. Other Libertas object types, includingLibertasDeviceandLibertasUser, 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 requestsnumber; 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.LibertasEnummay refine an integer-backed enum argument with the Enumeration name instyle; 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 MF1styleonnumber,date, andtimeis authoritative presentation metadata. There is no parallel Libertas syntax for specifying a message's number or date format. In particular, schemaFormatattributes 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 aLibertasDeviceID 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 schemaMoney.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, includinginteger,percentand0.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 DateOnly15 Long Valid Gregorian calendar date in yyyyMMddform, years 1000 through 9999 -
lmf.temporal.datetime-range:Argument Tag Avro payload Meaning and accepted values DateTime16 Long Unsigned Unix epoch seconds, from 0 through 253402214399 -
lmf.temporal.timeonly-range:Argument Tag Avro payload Meaning and accepted values TimeOnly17 Long Seconds within the civil day, from 0 through 86400 inclusive -
lmf.temporal.datetime-seconds:DateTimeuses 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:Rustlibertas_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 packageStringResourcescatalog;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; andlmf.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 hostlmf.resources.one-catalog: One App-package StringResources catalog owns stable schema keys and localized templates; functions and types carry no resource catalogslmf.resources.key-sets-match: Schema and default-document StringResources key sets match exactly; duplicate, missing or undeclared keys are errors