Architecture
Package graph
contracts ◄── client
▲ ▲
│ │
theme ◄── ui │
▲ ▲ │
│ │ │
websites ◄───┘ │
▲ │
│ │
chat apps
▲
│
plugins (manifest SDK, CLI, MCP server) ──► client
contractshas no UI and no network.themehas no React and no network.uidepends oncontracts+theme; React is a peer.websites,chat,appsare sibling surfaces. They do not depend on each other; when one needs
to render a canonical envelope it receives a renderer function as a prop.
pluginsis the server/tooling surface and depends onclient+contracts.
Data flow
HETIBANK API
│ canonical envelopes, figures with certainty, error envelope
▼
@hetibank/client (typed, injected transport, retries, request ids)
│
▼
your app state ──► @hetibank/websites / chat / apps ──► rendered UI
│
└─ presentation descriptor (text/markdown/component/full page)
The API computes; the SDK formats and presents. There is no second calculation path in the browser, the widget or the MCP server. That is the property that keeps figures identical across surfaces.
Trust boundaries
- Model/agent output is untrusted. Listing remarks, messages and tool text can carry injection.
Markdown is escaped; no raw HTML; message bodies are labelled as data, not instructions.
- The browser is untrusted for secrets. It gets a browser key and a token getter. Server keys
stay on the server; the plugin server holds them.
- Navigation carries identifiers, not authority. Paths, links and deep links never include
bearer tokens; the target reauthorizes.
- Widget results are partitioned.
structuredContentis model-visible;_metais widget-only.
Do not move private data into the model-visible channel.
- Brand cannot escalate. Theme tokens are presentation; a theme that changes a state color or an
action is a defect, and the contrast gate runs before shipping.
Presentation registry
One descriptor per capability says how a result may be shown:
PresentationDescriptor {
capabilityId, contractVersion, schemaId,
supportedViews: [text, markdown, component, full_page],
component { id, version, kind }, theme { id, version },
resource { uri, mimeType }, evidence { asOf, definitionIds, freshness },
allowedActions, fallback { text, markdown }, handoff { path, requiresAuth }
}
REST, MCP and the UI kits read the same descriptor instead of each surface registering presentation separately. fallback.text is mandatory: a host with no component support must still show something true.
Key decisions
| Decision | Why |
|---|---|
Plain CSS + --hb-* variables | Adoptable without our build tooling; brandable per subtree; no Tailwind version coupling |
| Injected transport and adapters | Tests, proxies, Electron shells and host apps plug in; packages stay pure |
| No client money math | One calculation path prevents cross-surface figure drift |
| Certainty on every figure | The product claim is “sourced and honest”; a bare number breaks it |
| Contrast gate throws | A shipped inaccessible theme is a defect, not a warning |
Chat/Apps receive renderComponent | Sibling packages stay independent; hosts choose the renderer |
| Plugin bundle = folder + sha256 manifest | Deterministic without a zip dependency; verifiable |
| PM board is generated from runs | Status cannot drift from evidence |
Failure modes the kit handles explicitly
| Failure | What the user sees |
|---|---|
| API 401/403 | DeniedState with request id |
| Expired session/link | ExpiredState with a re-auth action |
| Stale data | StaleState with the as-of date |
| Empty search | EmptyState, never an error |
| Missing figure | “Unavailable”, never 0 |
| Widget without host | WidgetFallback text |
| Contract drift | validation error / fallback, not a half-rendered card |
| Host without voice | voice button hidden; text input still works |
Repository layout
packages/ the eight packages
spec/ canonical OpenAPI + fixtures + curated surface
examples/ runnable reference compositions (website, chat, widget)
docs/ contract for contributors, provenance, guides
tools/ pm board, verify gates, shadcn catalog MCP
pm/ items + recorded runs + generated board
vendor/ frozen third-party mirrors (shadcn.io)