Build apps
Tutorial: Vehicle lease management
Run the reference app "Vehicle lease management" locally and read through its main files to learn the shape of an app built on Hane.
This page runs Hane’s reference app, “Vehicle lease management”, on your machine and walks through where everything is written. The reference app is a sample customer app built only with Hane’s public API (@hane/sdk) and UI library (@hane/ui). You can copy from it to start your own app.
What the app demonstrates
Section titled “What the app demonstrates”It is a business app that keeps a ledger of company car lease contracts and vehicles, and handles lease expiry and invoice reconciliation. All data is fictional; no real companies, vehicles, or people are included.
| Scenario | Hane features used |
|---|---|
| Ledger (contracts, vehicles, vendors) and value confirmation | Types and fields, value states and sources, rules, CSV import |
| Lease expiry (daily detection → case → Task for the assignee → decision → email requesting quotes) | Schedule, Workflow, assignment rules, approvals, Communication |
| One- or two-stage approval depending on the application amount | DeciderPolicy, operations performed by a person and the execution ledger |
| Invoice reconciliation | Reconciliation by rules, approval of inquiry emails with evidence |
| Documents (uploading contracts, search, checking extracted values) | Document service, extraction schemas |
| “Ask an agent” chat | Conversations, the chat agent, business agents, SSE |
Run it
Section titled “Run it”-
Start Hane locally
The chat agent and business agents run with the Claude Code driver, so check
claude auth statuson your machine before starting.ターミナルウィンドウ HANE_DEV_PROFILE=local-claude pnpm devWith the default
localprofile, screens, the ledger, and Workflows still work, but agents withdriver: claudefail. -
Load data
ターミナルウィンドウ pnpm dev:seedThis loads the organization
acme, the projectfleet, users, groups, the reference app’s service account and API key, definitions, and fictional data. -
Log in
Open http://localhost:5174 and pick fleet@example.test on the development IdP. Approvals are made by a different person than the requester, approver@example.test.
-
Ask something
The reference app’s agents work in Japanese. In Ask an Agent, write 「C-1001 の満了日を教えて」 (“When does C-1001 end?”) or 「C-1003 の再リースの見積を V-001 に頼んで」 (“Ask V-001 for a lease renewal quote for C-1003”). The quote request email becomes an approval Task. Log in again as approver@example.test and approve it in My tasks to send it (locally, the emulator receives it at http://localhost:8789).
File layout
Section titled “File layout”Directoryexamples/fleet/
Directorydefinitions/ manifests. All business rules live here
- bundle.yaml
Directoryschemas/
- …
Directoryrules/
- …
Directoryworkflows/
- …
Directoryschedules/
- …
Directorypolicies/
- …
Directoryagents/
- …
- …
Directorybff/ the server for the screens
- app.ts
- server.ts
- worker.ts
- ledger.ts
- agent-chat.ts
- agent-events.ts
- …
Directoryapp/ screens (React, TanStack Router)
- src/routes.tsx
- src/api.ts
- src/Agents.tsx
- src/theme.css
Directorydata/ CSV files of fictional data
- …
Read the definitions
Section titled “Read the definitions”Start with definitions/. The screens and the BFF contain no business decisions; they are all in definitions.
schemas/lease-contract.yamlis the contract type. The end date (end_date) and notice deadline (notice_due) arecomputed, decided by rules.rules/lease-end-date.yamlis the rule that calculates the end date from the start date and term in months. When the end date stated in the contract disagrees with the calculation, it doesn’t pick a winner and marks the value unknown (conflict).schedules/daily-expiry-scan.yamlcreates a case every morning at 7:00 for each active contract whose notice deadline is within 90 days.workflows/lease-expiry.yamlis the lease expiry process. If values are unconfirmed, it first creates a Task to check them, runs a drafting agent, and after the assignee decides on a course, moves through approval to the application.agents/assistant.yamlis the chat agent;agents/lease-qa.yamlandagents/quote-request.yamlare business agents.quote-requesthascommunication.send_emailand goes through approval with itsapprovalassignment.policies/holds assignment rules and DeciderPolicies. The rule that applications need one or two approval stages depending on the total amount is here.
Read the BFF
Section titled “Read the BFF”exchangeinbff/app.tsexchanges the user’s id_token and the app’s API key with Hane.createFleetAppcollects the/bff/*routes and returns Hane errors to the screens with their code and Problem intact.bff/server.tsis the local Node.js entry point. It logs in with PKCE against the development IdP, keeps sessions in memory, and exchanges again when the access token is about to expire.bff/worker.tsis the Cloudflare Workers entry point. It exchanges the Cloudflare Access token as the id_token and keeps no sessions.bff/ledger.tshas the routes for the ledger, cases, and Tasks. Updates send the record or Task version in If-Match withifMatch, and approval Tasks also send the digest of the ActionRequest shown on screen.bff/agent-chat.tsandbff/agent-events.tsare the conversation routes and the SSE relay.
Read the screens
Section titled “Read the screens”bffinapp/src/api.tsis the function that calls only the BFF’s/bff/*. On 401 it sends the user to login.app/src/routes.tsxcontains all routes. List filters are kept in the URL query.app/src/Agents.tsxbuilds the chat screen by passing aChatTransportthat calls the BFF toAgentChatfrom@hane/ui.app/src/theme.cssis an example of overriding the UI library’s default theme (--hane-*) to give the app its own look.
Field labels on screens come from the schema’s title, not from the app’s dictionary. Data from Hane (values, field names, Task titles) is shown as is, without translation.
Make it your own
Section titled “Make it your own”- Rewrite
definitions/for your own business types and processes, and check them withhanectl validate. - Keep the BFF’s
app.ts, and adapt the login entry points (server.ts,worker.ts) to your IdP. - Keep the screens calling
bff(), and rebuild them for your own business.