A minimal, well-commented skeleton application for the Hyperware platform using the Hyperapp framework. This skeleton provides a starting point for building Hyperware applications with a React/TypeScript frontend and Rust backend.
Example prompt (works well with Codex):
Use `kit new myappname --template lock-and-bind --ui`, (replacing myappname with appropriate app name) to make a template in `/desired_folder`, which you will modify to build the following app:
Insert your app spec here, e.g.:
Todo List with P2P Sync.
A collaborative TODO list where items sync between nodes.
Write a spec, and then implement it step by step. Use the README.md given in lock-and-bind to find instructions on specific details.
At the end, I should be able to run `kit bs —hyperapp` and manually test that the app works.
The rest of this document is aimed at LLMs not humans.
- Hyperware development environment (
kitcommand) - Rust toolchain
- Node.js and npm
Always build with
kit build --hyperapplock-and-bind/
├── Cargo.toml # Workspace configuration
├── metadata.json # App metadata
├── lock-and-bind/ # Main Rust process
│ ├── Cargo.toml # Process dependencies
│ └── src/
│ ├── lib.rs # Main app logic (well-commented)
│ └── icon # App icon file
├── ui/ # Frontend application
│ ├── package.json # Node dependencies
│ ├── index.html # Entry point (includes /our.js)
│ ├── vite.config.ts # Build configuration
│ └── src/
│ ├── App.tsx # Main React component
│ ├── store/ # Zustand state management
│ ├── types/ # TypeScript type definitions
│ └── utils/ # API utilities
├── api/ # Generated WIT files (after build)
└── pkg/ # The final build product, including manifest.json, scripts.json and built package output
The #[hyperprocess] macro is the core of the Hyperapp framework. It provides:
- Async/await support without tokio
- Automatic WIT generation
- State persistence
- HTTP/WebSocket endpoint configuration
ALL HTTP endpoints MUST be tagged with #[http]:
#[http]
async fn my_endpoint(&self) -> String {
// Implementation
}All remote requests must use .expects_response(30), where the value 30 sets a 30‑second response timeout.
let req = Request::to(("friend.os", "some-hyperapp", "some-hyperapp", "publisher.os"))
.expects_response(30)
.blob(LazyLoadBlob {
mime: None,
bytes: message,
})
.body(body);Parameters must be sent as tuples for multi-parameter methods:
// Single parameter
{ "MethodName": value }
// Multiple parameters
{ "MethodName": [param1, param2] }All keys in TypeScript need to stay in snake_case (node_id), camelCase (nodeId) will break the app!
export interface StatusSnapshot {
node_id: string;
}MUST be included in index.html:
<script src="/our.js"></script>Your app's state is automatically persisted based on the save_config option:
OnDiff: Save when state changes (strongly recommended)Never: No automatic savesEveryMessage: Save after each message (safest; slowest)EveryNMessage(u64): Save every N messages receivedEveryNSeconds(u64): Save every N seconds
Edit AppState in lock-and-bind/src/lib.rs:
#[derive(Default, Serialize, Deserialize)]
pub struct AppState {
// Add your fields here
my_data: Vec<MyType>,
}For UI interaction:
#[http]
async fn my_method(&mut self) -> Result<String, String> {
// Parse request, update state, return response
}Add system permissions in pkg/manifest.json:
"request_capabilities": [
"homepage:homepage:sys",
"http-server:distro:sys",
"vfs:distro:sys" // Add as needed
]These are required to message other local processes. They can also be granted so other local processes can message us.
If sending messages between nodes, set:
"request_networking": true,- Add types in
ui/src/types/lock-and-bind.ts - Update store in
ui/src/store/lock-and-bind.ts - Modify UI in
ui/src/App.tsx
- Check parameter format (tuple vs object)
- Verify
/our.jsis included in index.html - Check that the app is running in Hyperware environment
- Use simple types or return JSON strings
- No HashMap (use Vec<(K,V)>)
- No fixed arrays (use Vec)
- Add #[derive(PartialEq)] to structs
- Import the most important structs and functions from
hyperware_process_lib, e.g.Request,LazyLoadBlob,ProcessId
- Run
kit b --hyperappto generate it
- No struct/enum/interface name is allowed to contain digits or the substring "stream", because WIT doesn't allow it
- No record/variant/enum name is allowed to end with
Request,Response,RequestWrapper,ResponseWrapper, because TS caller utils are autogenerated with those suffixes
Carefully read the prompt; look carefully at instructions.md (if it exists) and in the example-apps directory.
In particular, note the example applications example-apps/sign/, example-apps/id/, and example-apps/file-explorer.
Note that file-explorer example contains an api folder, which is generated by the compiler, and not human or LLM written.
sign and id demonstrate local messaging.
file-explorer demonstrates VFS interactions.
Note in particular that bindings for the UI will be generated when the app is built with kit build --hyperapp.
As such, first design and implement the backend; the interface will be generated from the backend; finally design and implement the frontend to consume the interface.
Subsequent changes to the interface must follow this pattern as well: start in backend, generate interface, finish in frontend
Do NOT create the API. The API is machine generated. You create types that end up in the API by defining and using them in functions in the Rust backend "hyperapp"
Look carefully at IMPLEMENTATION_PLAN.md and in the example-apps/ directory, if relevant.
Work from the existing template that exists at lock-and-bind/ and ui/.
Note in particular that bindings for the UI will be generated when the app is built with kit build --hyperapp.
As such, first design and implement the backend; the interface will be generated from the backend; finally design and implement the frontend to consume the interface.
Subsequent changes to the interface must follow this pattern as well: start in backend, generate interface, finish in frontend
Do NOT create the API. The API is machine generated. You create types that end up in the API by defining and using them in functions in the Rust backend "hyperapp"
Do not worry about serialization/deserialization when using send and send_rmp functions for p2p communication.
Notice that this all happens within those functions: just take the rust types as args and return rust types as return values.
If you create a GUI for the app you MUST use target/ui/caller-utils.ts for HTTP requests to the backend.
Do NOT edit this file: it is machine generated.
Do NOT do fetch or other HTTP requests manually to the backend: use the functions in this machine generated interface.