Designing an App
Load this card for every App-authoring task. It holds the rules of the App contract that apply to any App: the smallest complete design, asking once, the data-first model, types and Nullable choices, discovery and dependencies, function permissions, the task lifecycle, documentation, wire compatibility and Studio's compile loop. Its rules are generated from the App contract, the MCP tools and the shared reference, which own them.
Load the card for each feature the App uses as well:
AI Import for a page whose values come from a document,
messages for StringResources templates and formatted
values, network access for AccessHost, the
Endpoint protocol, Endpoint callback,
AI Search and persistent data cards for those
features, and deploying and publishing when the
user asks for it.
Order of work
- Design the data interaction first, with every behavior owned once and each
fact asked once (
app.design.*,app.ask.*,app.model.*,app.ai.*); presentation follows (app.template.*). - Choose a type for each occurrence (
app.types.*,app.rows.*,app.nullable.*,app.representation.*) and keep published shapes compatible (app.wire.*). - Declare the package: identity, entry functions, exports and dependencies
(
app.identity.*,app.discovery.*,app.external.*), then functions, permissions and the task lifecycle (app.function.*,app.task-creation.*,app.permissions.*,app.persistence.*,app.version.*,app.removal.*), using the installed SDK's runtime APIs (sdk.*). - Document every schema-visible node (
app.docs.*) under the source documentation card. - Compile with Studio until validation succeeds (
app.compile.*,deploy.mcp.*), checkapp.checklist.*, then validate and deliver.
Rules to apply
Studio does not check these rules. Each is quoted from its owner.
Required design principle
Owner: app-contract.md#required-design-principle
app.design.smallest-complete(judgment): The goal is the smallest design that remains logically complete. All App authors, including CodeBots, must follow this principle. It is a requirement, not an optional style preference.app.design.complete-with-owners(judgment): Implement every required behavior, condition, and outcome, with a clear owner for each responsibility. Use the platform's defined behavior and normal App lifecycle instead of duplicating them with additional mechanisms. Add App logic when the application's requirements need it; simplicity must not omit necessary validation, permissions, consistency, or user control.app.design.natural-data-model(judgment): Model data in its natural form. Declare each stored type as what the data is: its real fields, one owner per fact, and a Union where a value is one of several shapes. Never reshape stored data for a screen, a workflow step, or a question order. Human-facing types, the Request and Response payloads a person edits and views, are views of that data: they may wrap it as the person's path needs, for example as a wrapper member within a Union, while the stored model stays as it is.app.design.human-shortest-path(judgment): The shortest path is the person's. Human usability is part of what the smallest design optimizes: the fewest steps, questions, and screens a person needs to reach a complete, correct answer. A design that is shorter for its author or a CodeBot never justifies a longer path for the person.app.design.no-contradiction(judgment): The two never conflict. The goal is human first, and the natural data model serves it: the platform presents the model in its natural form along the person's shortest path, so neither is traded for the other. Where they seem to conflict, the platform is missing a presentation mechanism; raise that gap instead of bending the data or lengthening the person's path. Presentation and interaction adapt to the model through the platform: schema attributes such asAskOnAdd,ReadOnly, andHidden, and Display Template rules such aslock,flatten,promote, and layouts. When the platform has no way to present a natural model, that is a platform gap to raise, not a reason to bend the data.
Ask once, directly
Owner: app-contract.md#ask-once-directly
app.ask.no-gate-question(judgment): A question exists only to collect a fact. Do not add a Boolean, Yes/No or "something else applies" switch whose only effect is to reveal a follow-up asking the same thing in detail: the person answers twice, the App needs extra navigation and state, and the stored switch can disagree with the real answer.app.ask.detailed-question(judgment): Ask the detailed question itself: a Bitflags or Enumeration list with a "None of these" member, a List that may be empty, or a required Union whose variants carry their own data. Keep a separate question only when its answer decides something the detailed answer cannot.
AI access in every design step
Owner: app-contract.md#ai-access-in-every-design-step
app.ai.document-pages-import(judgment): A page whose values come from a document declares document import.
Application model
Owner: app-contract.md#application-model
app.model.no-invented-interaction(judgment): Background tasks and TypesOnly packages retain their ordinary roles and need no invented user interaction.app.model.data-first(judgment): Design the data interaction first, then choose its presentation.app.model.implement-once(judgment): Implement the operation once in the task; the GUI, AI and software interfaces derive from that common description. A person can use the GUI directly without AI involvement.app.model.access-checks: Each participant remains subject to its applicable access checks. Generated clients follow the human interaction lifecycle; software may agree with the service on its own ordering within the declared protocol.ExcludeUIcan limit the GUI and AI projection while retaining the underlying Endpoint contract.app.template.not-schema: The template is mandatory, but an App with neither compiles to the valid empty template. It is not part of the schema, and changing only the template never changes the App version.app.template.edit-source: Editdisplay.jsonor source attributes, never the compiled template.app.model.no-second-schema: Do not create a second JSON schema, manually assign schema IDs, or treat generated schema JSON as source.
Project and discovery rules
Owner: app-contract.md#project-and-discovery-rules
app.discovery.entry-functions: Declare task entry functions insrc/lib.rsfor Rust or Studio's selected entry module for TypeScript. Other implementation can live in ordinary modules. Function re-exports do not replace these declarations. The code organization practice explains module boundaries and exact-name type exports.app.discovery.ordinary-roots: An ordinary App uses every non-ignored public/exported function as a root. Its published type set is the minimum transitive closure needed by:app.discovery.unreachable-unpublished: An unreachable public type is not published by an ordinary App.
Schema compilation with Codex
Owner: app-contract.md#schema-compilation-with-codex
app.compile.edit-validate-loop(judgment): After changing public functions, types, attributes, or dependencies, save the files, compile the affected project, fix the reported source errors, and repeat until validation succeeds.app.compile.edit-source: Edit source declarations rather than generated schemas. Continue to run language builds and tests separately.app.compile.saved-files-only: Compilation uses saved files and installed or locally resolvable dependencies. It does not read unsaved editor buffers, install missing dependencies, overwrite source or generated files, or deploy. No Hub connection is required.app.compile.success-scope: Success means source schema validation passed; it does not establish exact deployed dependency-version bindings or replace Studio's deployment checks.
Type model
Owner: app-contract.md#type-model
-
app.types.closest-native(judgment): Use the closest native type. Do not encode typed values as ad-hoc strings. -
app.types.integer-width:Libertas meaning TypeScript Rust Signed/unsigned integer numberplus@LibertasNumber(type=...)concrete i*/u* -
app.types.date-time-encoding:DateOnlyis an integeryyyyMMddcalendar tuple, not an epoch timestamp.TimeOnlyis seconds from midnight.DateTimeis epoch seconds presented in the viewer's local timezone.
User-facing and non-user-facing types
Owner: app-contract.md#user-facing-and-non-user-facing-types
-
app.types.review-occurrences(judgment): Review each occurrence of a type, including its editability and visibility, rather than classifying it bypuborexportalone. -
app.types.separate-input-internal(judgment): Reuse a type when these meanings and constraints agree. Use separate input and internal types when they differ, and map between them explicitly. For example, an editor can use blank text while a stored record uses absence. This does not justify copying every type into a second hierarchy. -
app.types.input-optional-text:Meaning Recommended representation Ordinary optional text, where blank means not supplied String with Default="",SizeMin=0, and a suitable maximum. -
app.types.input-required-text:Meaning Recommended representation Required text String with SizeMinof at least 1; omit a Default when the person must supply the answer. -
app.types.input-optional-override(judgment):Meaning Recommended representation An optional override of a value the App computes or receives, or harmless supplementary detail Nullable ( Option<T>in Rust), with null meaning “use the computed or received value” or “not given”, stated in the description. For output and internal data, Nullable also marks an unavailable value as distinct from zero. -
design.nullable.explicit-domain-choice:Meaning Recommended representation A yes-or-no decision whose “yes” carries details, or an optional override Nullable labelled with [NullText]and[SetText]; no Default when the person must answer,$nullwhen skipping means null. See Nullable choices. -
app.types.input-optional-min-length:Meaning Recommended representation Optional text that must meet a minimum length when supplied Nullable String with the appropriate size bounds; null remains valid. -
app.types.input-named-alternatives(judgment):Meaning Recommended representation A decision between named alternatives, including “none” and “provide details” A labeled Enumeration for choices without payloads, or a labeled Union when a choice carries additional input. -
app.types.input-folding(judgment):Meaning Recommended representation Details that only need to expand or collapse A Table with folding. Folding changes presentation without changing the answer.
Rows a person adds
Owner: app-contract.md#rows-a-person-adds
app.rows.service-rows-readonly(judgment): When the service creates the rows and the person only edits them, mark the ListReadOnly, which fixes its membership while its members stay editable (ReadOnly Lists).
Nullable choices
Owner: app-contract.md#nullable-choices
app.nullable.label-wording(judgment):NullTextis a complete answer;SetTextintroduces the value entered right after it, so it reads as a lead-in to that value. For example: “Alive at the end of 2026” and “Died in 2026 on” followed by the date; “Use the payer's basis” and “My own basis is” followed by the amount; “Not a real estate professional” and “Real estate professional” followed by the qualifying details.app.nullable.fallback-labels(judgment): The defaults “None” and “Specify” are only a client fallback; they never explain the decision.app.nullable.must-answer: If the person must answer, give no Default. The choice then starts with no branch selected and blocks Submit until the person chooses, exactly like a Union without a Default.app.nullable.skip-means-null: If skipping means null, such as an optional override of a value the App computes or receives, or optional detail whose omission decides nothing, give the reserved Default$null(Rust#[libertas_default($null)]). The choice starts on the null branch, and the description says what null means.app.nullable.usual-value-default(judgment): If one value is the usual answer, give that value as the Default; the choice starts on the value branch with it.app.nullable.three-answers(judgment): Use a labelled Enumeration or Union instead when there are three or more answers, or when the negative answer carries data of its own. For output and internal data, which no person answers, Nullable remains the normal way to state that a value is absent.app.nullable.native-names(judgment): NativeNone/SomeorNothing/Somethingnames never explain a decision; this is not the gate Ask once, directly forbids, because the value branch carries the details itself and the person answers once.app.nullable.option-string-min: AnOption<String>withSizeMin=1is valid: null is allowed, but a present empty String is rejected. It therefore differs from a non-Nullable String withSizeMin=0. Use the latter for ordinary optional text when that distinction serves no purpose. Size bounds do not supply initial values.app.nullable.unanswered-start: Under the initialization contract, when no Fixed, Schema Default, or localized default supplies a value, a String starts unanswered even if it permits empty text, an editable Nullable starts with no branch selected, and a Union starts unselected.app.nullable.default-not-confirmed(judgment): A Default selecting “none” does not establish that the person confirmed that choice.
Reviewing a representation change
Owner: app-contract.md#reviewing-a-representation-change
app.representation.review-uses(judgment): When changing Nullable text to blank text, review presence checks, validation rules, label fallbacks, calculations, and grouping keys. Translate""to absence only where the destination uses that meaning, and translate absence back to""for that editor. Do not accidentally group unrelated unnamed records together. Keep unknown amounts distinct from zero.app.representation.update-docs-import(judgment): Update source comments and AI Import declarations and instructions to match the new blank semantics: making manual entry optional does not enable blank import results, which follow the optional-text import rule. Inspect the generated schema and document, and validate the affected entry and import behavior.app.representation.compatibility(judgment): Review published protocol and persistent-data compatibility before changing any representation. Replacing Nullable with a String or custom Union is not automatically compatible. Follow the data-type evolution contract; changing an editor does not require changing an existing stored representation.
External types and App dependencies
Owner: app-contract.md#external-types-and-app-dependencies
app.external.foreign-type-metadata: The official type supplies inherited schema/document metadata when consumers materialize the graph, while explicit compatible attributes override same-name official attributes.app.external.no-authored-dependencies: Do not write versionedAppDependenciesin source or edit generated locks. Studio discovers and verifies dependencies, deploys them first, and records the actual exact versions. The deployment dependency contract defines discovery, including which Endpoint constraints create a dependency, Plaza preflight, installation, failure handling, and the exactVersionpin.
Functions, permissions, and singleton tasks
Owner: app-contract.md#functions-permissions-and-singleton-tasks
app.function.singleton:Singleton, limiting the Hub to at most one task for the App and native function name across versions.app.permissions.package-owned-reasons(judgment): Different Apps can request the same permission for different reasons, so explanations are package-owned rather than global.
Task version selection
Owner: app-contract.md#task-version-selection
app.version.selection-unchanged: A task runs the App version selected for it. Installing or upgrading an App never changes any task's selection.app.version.change-permission: A person with configuration permission on the task changes it through the task's settings; selecting a version of another App retargets the task and also requires configuration permission on that App, as task creation does.app.version.change-keeps-state: The Hub then replaces the task's runtime with the selected version, keeping its arguments and enabled state. When the arguments cannot be initialized under the selected version, the change fails and the task keeps its version.app.version.directional-evolution: Which version reads the data another wrote follows the directional evolution rules.
Task removal
Owner: app-contract.md#task-removal
app.removal.permissions: The user needs removal permission on the selected task and effective configuration permission on every affected dependent task. Dependencies alone produce a localized warning listing the affected tasks, with a choice to cancel or continue. Missing configuration permission produces a localized failure identifying the tasks that need permission; removal cannot continue. The Hub's object-removal handling performs the actual configuration cleanup.app.removal.dependents: Removing a task removes its persistent data. The confirmation lists every affected task together with the effect on it. An argument of another task that bound an Endpoint of the removed task is set to null. The Hub then validates that task's arguments: when they are still valid, the task is restarted automatically with the new arguments; otherwise it is disabled with the new arguments.app.removal.atomic: Removal is atomic. The Hub applies it in the order of the dependency map: the removed task and its data, then each dependent task's nulled arguments and its restart or disabling. Either the whole removal takes effect or none of it does; no person or task observes a partial state, and a removal interrupted by a power loss is completed before the Hub resumes ordinary operation. Older Hubs retain their supported removal operation; the clients warn that dependency and permission preflight requires a Hub update.
Documentation and localization
Owner: app-contract.md#documentation-and-localization
app.docs.first-line-label: the first documentation line is the display name;app.docs.description-lines: remaining ordinary lines are the description;app.docs.persisted-text-wins: persisted or user-assigned text, including an empty string, is immutable to programmatic defaulting; these defaults apply only while creating a value for which no persisted value exists;app.docs.list-divider: list documentation may contain a line of at least four hyphens; content above the divider describes the list and content below describes each item; andapp.docs.divider-before-properties: the array divider must appear before custom document properties belonging to the list item.app.docs.plain-comments: Plain code comments are not localized schema documentation.app.docs.every-node-labelled: Every authored package, function, named type, parameter, field, List container and item, Enumeration member, Union variant, and inline value must have a non-empty natural label and description that make sense without source names or implementation vocabulary. Custom document values and package string resources follow the same user-language and accessibility rules. The complete normative contract is the schema documentation invariants.app.docs.function-comments(judgment): Every public function comment must additionally satisfy the developer function-comment invariants. Its description is a complete, actor-explicit account of the function's purpose, user choices and actions, automatic behavior, outputs, and material limits; it must not rely on generated type text to fill semantic gaps.
Wire and compatibility discipline
Owner: app-contract.md#wire-and-compatibility-discipline
app.wire.attributes-keep-wire-type: Attributes such asFormat,PhysicalUnit,DateOnly,Bitflags,ValidationRules, chart metadata,ReadOnly,Hidden, andExcludeUIaffect validation/presentation without changing the underlying wire type.ExcludeUIis specifically a Control-plane projection: the Hub omits a marked server Endpoint from its Control UI-device list and generated clients omit marked request operations. It never removes nodes from admin creation, editing, approval, persistence, routing, or authorization. See the Endpoint declaration contract.app.wire.union-order: Union declaration order is significant.variant_index!uses declaration order and append-only protocol/data Union evolution depends on it.app.wire.append-only: Append new variants for compatible Union evolution; do not insert, remove, reorder, or change existing payload shapes in a published append-only line.
MCP deployment
Owner: deployment.md#mcp-deployment
-
deploy.mcp.rule-debugging-input:Rule debugging tool Input and result evaluate_validation_rulesprojectand optionalentryidentify saved App source as forcompile_schema;typenames a Request variant or data type. Supply eithervaluein schema JSON value form oravroPath, a saved Avro request file. Optionalphaseisfinal(default) orincremental. Returns structured rule traces and a short text summary, with compile diagnostics. -
deploy.rule-debugger.no-hub-or-submit: Rule debugging compiles the same saved source and materialized dependencies ascompile_schema, then uses Studio's ordinary rule evaluator. It needs no Hub connection and never submits the supplied request. -
deploy.mcp.rule-debugging-values: JSON supplies the named Request's payload; omitted members remain unanswered and explicit null remains absence. A saved Avro Request includes its protocol Union discriminant.avroPathis absolute or relative toproject. See Debugging rules for outcomes, lazy reads, developer presentation, and CI fixtures. -
deploy.mcp.preview-display:preview_displayshows one screen of the saved workspace App in Studio's Display Template preview, Hub-independent likecompile_schema. It takes the absolute project directory, the protocol variant (a Union member such as a Response) or function name to show, a deviceform(phone,tablet,desktop, ortv), optionalwidth,height,orientation,pointer, andthemesize-class/condition overrides, and an optional Libertas Schema Pathselectnaming a node to inspect. It compiles the project, then opens a preview if none is open for it or refocuses the open one and switches it to this screen and device; later saves re-render the same preview live. Its JSON result reportspreview(opened,refocused, orheadlesswith areason), the resolved device conditions and window, the root node's resolved layout, style, and hints, and, whenselectis given, the matched rules in cascade order with their winning members and each rule's source (adisplay.jsonline or a source attribute's file and line). Compile diagnostics are included, andtemplateCurrentisfalsewhen a failed compilation leaves the last good template on screen. It requires VS Code and Libertas Studio to be running and fails explicitly otherwise.
Shared detailed reference
Owner: index.md#shared-detailed-reference
sdk.owns-runtime-apis: The installed public SDK owns exact runtime APIs:@libertas-ai/typesfor TypeScript andlibertas-rsfor Rust. Use the version selected by the project. Chart references define their versioned contracts and fallback behavior; they do not certify every older client build.
Rules Studio enforces
Studio rejects a source that breaks one of these rules. The owner holds the full rule.
Application model
Owner: app-contract.md#application-model
app.identity.package-name: An App identity has exactly one hyphen joining two lowercase ASCII snake_case names without edge or repeated underscores
Project and discovery rules
Owner: app-contract.md#project-and-discovery-rules
app.discovery.types-only-helpers: A public helper function in a TypesOnly package must be ignored
Schema compilation with Codex
Owner: app-contract.md#schema-compilation-with-codex
app.compile.project-directory: Supply the absolute project directory to compile_schema; an optional entry selects the entry fileapp.compile.dependencies-complete: Missing, ambiguous or cyclic App dependencies fail compilation; a partial bundle is not a successful result
Rows a person adds
Owner: app-contract.md#rows-a-person-adds
app.rows.hidden-needs-value: Every Hidden field in a row a person can add needs a Default, Fixed or CopyFrom