Skip to content
HaneDOCS日本語

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.

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
  1. Start Hane locally

    The chat agent and business agents run with the Claude Code driver, so check claude auth status on your machine before starting.

    ターミナルウィンドウ
    HANE_DEV_PROFILE=local-claude pnpm dev

    With the default local profile, screens, the ledger, and Workflows still work, but agents with driver: claude fail.

  2. Load data

    ターミナルウィンドウ
    pnpm dev:seed

    This loads the organization acme, the project fleet, users, groups, the reference app’s service account and API key, definitions, and fictional data.

  3. 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.

  4. 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).

  • 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
      • …

Start with definitions/. The screens and the BFF contain no business decisions; they are all in definitions.

  • schemas/lease-contract.yaml is the contract type. The end date (end_date) and notice deadline (notice_due) are computed, decided by rules.
  • rules/lease-end-date.yaml is 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.yaml creates a case every morning at 7:00 for each active contract whose notice deadline is within 90 days.
  • workflows/lease-expiry.yaml is 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.yaml is the chat agent; agents/lease-qa.yaml and agents/quote-request.yaml are business agents. quote-request has communication.send_email and goes through approval with its approval assignment.
  • policies/ holds assignment rules and DeciderPolicies. The rule that applications need one or two approval stages depending on the total amount is here.
  • exchange in bff/app.ts exchanges the user’s id_token and the app’s API key with Hane. createFleetApp collects the /bff/* routes and returns Hane errors to the screens with their code and Problem intact.
  • bff/server.ts is 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.ts is the Cloudflare Workers entry point. It exchanges the Cloudflare Access token as the id_token and keeps no sessions.
  • bff/ledger.ts has the routes for the ledger, cases, and Tasks. Updates send the record or Task version in If-Match with ifMatch, and approval Tasks also send the digest of the ActionRequest shown on screen.
  • bff/agent-chat.ts and bff/agent-events.ts are the conversation routes and the SSE relay.
  • bff in app/src/api.ts is the function that calls only the BFF’s /bff/*. On 401 it sends the user to login.
  • app/src/routes.tsx contains all routes. List filters are kept in the URL query.
  • app/src/Agents.tsx builds the chat screen by passing a ChatTransport that calls the BFF to AgentChat from @hane/ui.
  • app/src/theme.css is 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.

  1. Rewrite definitions/ for your own business types and processes, and check them with hanectl validate.
  2. Keep the BFF’s app.ts, and adapt the login entry points (server.ts, worker.ts) to your IdP.
  3. Keep the screens calling bff(), and rebuild them for your own business.