App code organization
This Rust and TypeScript practice supports Libertas's collaboration goal. Adapt the example directory names to the project. Preserve working conventions and start with the fewest modules the App needs. The App contract and language manuals own the platform requirements.
Organize around data interaction. Domain data, operations and behavior form one App model for people, AI and other Apps. Its data also supplies the GUI. Keep one implementation of each operation and let the platform derive its interfaces under the shared interaction model.
Choose boundaries by responsibility
Keep code that changes for the same reason together. A small App may put all of these responsibilities in its entry file. Split a module when its responsibility becomes difficult to navigate or can be understood and tested independently; file length alone does not require another layer.
| Owner | Contains | Boundary |
|---|---|---|
| Package entry | Package documentation/directives, explicit public type exports, documented task entry functions | Make the published surface easy to review; delegate runtime work as it grows. |
| Domain feature | Its values, rules, calculations and interaction handlers | Name it for the domain, such as catalog or scheduling. Keep feature-specific helpers here. |
| Protocol and stored contracts | Each Endpoint protocol Union and persistent-data Union, referring to feature payload types | Keep each published Union's declaration and append-only order in one obvious place. |
| Task runtime | Startup, task state, SDK callbacks, dispatch and shutdown | Own runtime lifetimes and pass the context handlers need. |
| SDK access | The actual persistence, device or external-service calls and App-specific consistency work | Extract a small module when it helps; use SDK types directly. |
| Presentation source | Source metadata beside the value it describes; optional project-root display.json |
Arrange the same data; reuse generated controls and navigation. |
| Tests | Domain examples and interaction outcomes | Exercise behavior and contracts, using synthetic fixtures. |
A feature can grow from one file into types, rules and interaction modules.
Move a helper into a specifically named shared module when multiple features
need the same meaning; a general utils module obscures ownership. Add an
interface or adapter for a real implementation boundary or test substitute.
Splitting files alone does not justify repository, service, controller or mapping
abstractions around an existing SDK API.
Keep dependencies easy to follow
When these responsibilities have separate modules, the entry composes the runtime and the runtime dispatches to feature handlers. Handlers use domain rules and the SDK operations they need. Rules work on typed values and remain independent of callbacks, storage handles and presentation where their behavior permits it. Protocol and stored contracts refer to the domain payload types. Internal modules import from the defining module instead of routing their dependencies through the App's public entry file.
Reuse a type when its meaning and constraints are the same. Use distinct input, output or stored types when their actual semantics differ; follow type selection by occurrence. A file boundary is not a reason to copy the same record into another shape.
The task runtime owns shared state and identifies per-user or per-peer state explicitly. Handlers receive the state and identity needed for their work. Keep workflow decisions and domain checks with the operation that owns them; all callers reach that same behavior. SDK callbacks, subscriptions and timers retain their documented ownership and lifetime. Use normal startup and the Data API responsibility boundary for persistence failures.
Rust layout
Rust's ordinary module files and explicit re-exports let implementation files move while the library's public API stays clear. The following adapts the Rust module conventions and Cargo package layout to a Libertas library:
Cargo.toml
display.json optional presentation source
src/
lib.rs package text, public exports, task entry functions
configuration.rs task arguments and configuration types
protocol.rs stable protocol Unions
data.rs stable stored-data Unions and records
runtime.rs task lifecycle and dispatch
catalog.rs feature module declarations and local exports
catalog/
types.rs feature values and protocol payloads
rules.rs domain checks and calculations
interaction.rs operation handlers
storage.rs App's Data API calls, when useful
tests/
interactions.rs public behavior scenarios
fixtures/ synthetic test data
Keep the library and documented public task functions in src/lib.rs, with
configuration arguments and function attributes. Delegate runtime work as it
grows, keeping implementation functions private or pub(crate) as appropriate.
Function re-exports do not replace entry-file task declarations.
Every published named schema type must remain an
exact-name root export,
including nested payload types reached through the protocol or data schema.
Use explicit pub use declarations so a change cannot silently enlarge the
published surface. TypesOnly packages require explicit type exports and have
no executable task entries. Follow the Rust discovery and scope rules.
An entry-file excerpt showing module wiring only:
mod configuration;
mod protocol;
mod data;
mod runtime;
mod catalog;
mod storage;
pub use configuration::Configuration;
pub use protocol::Protocol;
pub use data::StoredData;
pub use catalog::{Item, SaveItem};
The named declarations must exist and be documented in their owning modules;
catalog.rs explicitly re-exports its public types. Add every other reachable
named type to the root exports. This wiring excerpt omits package text and the
task function; it is not a complete App.
Place focused unit tests beside the rules they test under #[cfg(test)];
use tests/ for tests through the library's public API. This follows
Rust test organization.
Preserve an existing choice of module files or mod.rs; either follows native
Rust resolution. Create a separate crate or workspace member when it is a real
reusable package, not merely to shorten a file. A new Libertas package introduces
ownership and deployment dependencies under the App dependency contract.
TypeScript layout
TypeScript is under development and not release ready. Check the
development status and matching SDK prerequisite
before choosing this path. Organize it as an ESM project using the selected
Studio entry module and the strict configuration baseline.
For that NodeNext baseline, set "type": "module" in package.json.
package.json
tsconfig.json
display.json optional presentation source
src/
index.ts package text, public exports, task entry functions
configuration.ts task arguments and configuration types
protocol.ts stable protocol Unions
data.ts stable stored-data Unions and records
runtime.ts task lifecycle and dispatch
catalog/
types.ts feature values and protocol payloads
rules.ts domain checks and calculations
interaction.ts operation handlers
storage.ts App's Data API calls, when useful
tests/
interactions.test.ts behavior scenarios using the project's runner
fixtures/ synthetic test data
Declare documented exported task functions in the selected entry module, with configuration arguments and function annotations. Delegate runtime work as it grows. Export helpers from their defining module when other implementation files need them, keeping them out of the App entry's exports. Function re-exports do not replace entry-file task declarations.
Use named, exact-name exports for every published schema type. Prefer explicit
type-only imports/exports for interfaces and type aliases; export an enum or
other value normally when runtime code needs its value. These distinctions
follow the TypeScript module reference.
Keep imports resolvable under the project's ESM configuration. For the NodeNext
baseline, relative specifiers use the emitted .js paths that TypeScript
resolves to source .ts files.
An entry-file excerpt showing type exports only:
export type { Configuration } from "./configuration.js";
export type { Protocol } from "./protocol.js";
export type { StoredData } from "./data.js";
export type { Item, SaveItem } from "./catalog/types.js";
The excerpt assumes the named declarations are interfaces or type aliases and
are documented in their defining files. It omits the package text, task function
and runtime imports. Add every other reachable named type to the entry exports.
Use direct imports between implementation modules and avoid chains of
export * files. A source file needing only module isolation can use export {};
keep ordinary App implementation out of the global namespace.
Use the project's existing test runner. Tests may sit beside the implementation
or in tests/, provided they are not App entry exports or runtime dependencies.
Introduce TypeScript project references
only when multiple real projects need separate builds or dependency boundaries;
ordinary modules are enough for one App. Native project structure does not
replace Libertas package ownership or Studio's dependency resolution.
Make a change through its owners
- Locate the published Request, Response or stored variant and its feature payload type. Read the source documentation and applicable contract.
- Change the feature's domain rule and handler. Keep persistence or device effects in the module that performs them, and use that behavior for human, AI and software invocation.
- Update the single protocol/data Union when a new variant is required. Preserve its name, existing cases and order under data-type evolution. Update exact-name root exports and localized source documentation together.
- Update presentation source only when the same data needs a different arrangement. Generated schema, localized documents, compiled templates and deployment bindings remain generated under the authoring contract.
- Check the affected behavior with meaningful examples. Cover relevant state transitions, invalid input, identity/access, conflicting updates and external effects; use the App's requirements to select the cases.
For a file-only reorganization, keep task entry names, published type identities, Union order, field shapes, annotations and localized meaning unchanged. Run the native checker and Studio validation, and review the generated schema, document and Display Template for unintended changes. Follow the validation and handoff workflow.
Review checklist
- Can a CodeBot find the entry function, each contract and the owning handler without tracing generic utility or export chains?
- Does each domain rule and effect have one owner, shared by all participants?
- Are root exports intentional and all schema-visible declarations documented?
- Are protocol and stored Union identities and order preserved?
- Does each module depend only on what its responsibility needs?
- Are extra folders, wrappers and packages justified by real behavior?
- Do native checks, Studio validation and relevant behavior tests pass?