Hugs
A hug has two parts you write: its definition (hug.ts, with define_hug) and its page (src/main.ts, with
mount_hug). Frontend authors install one package, @getcliqio/hug-frontend. The tutorial
builds one end to end; mount_hug in depth, UI kit, React,
Files and links and Testing go further.
The definition — define_hug
hug.ts
file({ accept, max_bytes, multiple, optional })makes a field an upload slot.accepttakes MIME types (image/*) and extensions (.pdf);multipletakes up to 10 files. The answer holds a reference ({ artifact_id, name, size, content_type, sha256 }), never the bytes..describe('…')on a field is its label in the default read-only view.- Parameterized hugs —
values,filesandsummarymay depend on the params, for an answer whose shape depends on what was asked: one verdict per document asked about;form’s fields, listed in its params. Both checks — the page’s and the agent’s — use the Hug’s own params. - Rules across fields go in the schema:
superRefinewith paths (ctx.addIssue({ path: [i, 'note'], … })), ordiscriminatedUnionfor answers of different shapes (approve or reject). define_hugis pure (Zod only), so the same file runs in the page, in agent code and — compiled todist/definition.mjs— in cliqd and LLM agents.
check_params(raw), check(raw, params) (both return
{ ok: true, value } or { ok: false, problems: [{ path, message }] }), values_schema(params), files(params),
summary(values, params), describe().
The page — mount_hug
src/main.ts
edit receives:
What
mount_hug does for you: connects to the Hub page and applies its theme; starts from the person’s draft,
else their earlier answer, else initial; checks the params (a bad one shows an error panel); after every set or
replace, checks the answer with the definition and tells the Hub whether it’s valid, its problems, the Submit label
and the summary; switches between edit and view; keeps the frame’s height fitted.
Fields that depend on other fields are plain conditions in edit (it redraws on every change). Clear a field you
hide with set(path, undefined), and put the same rule in the definition.
html is lit-html: @event=${fn}, .property=${v},
lists with map, nothing for an empty branch.
The UI kit
import '@getcliqio/hug-frontend/ui' registers web components that match the Hub — its font, colours (light and
dark) and spacing — and are keyboard-reachable and labelled. form and approval are built only from these.
Theme: the kit uses CSS variables the Hub sets —
--cliq-bg, --cliq-panel, --cliq-ink, --cliq-ink-2,
--cliq-ink-3, --cliq-line, --cliq-acc, --cliq-ok, --cliq-warn, --cliq-bad. Use them in your own CSS too.
A page that doesn’t use the kit can still load the Hub’s font from @getcliqio/hug-frontend/ui/fonts.css.
React
cliq hug new <name> --react scaffolds a React page:
src/main.tsx
useHug(definition, { initial, submit }) returns { params, value, set, replace, problems, hug }; <HugRoot> mounts
it and <DefaultView> draws the default read-only view.
What a page can’t do
The page runs in a sandboxed frame with no network (Security):Testing
The definition is plain code — testcheck, summary and files directly (see the tutorial).
For the page, mock_host from @getcliqio/hug-frontend/testing stands in for the Hub:
mock_host({ params, mode, mine, viewing, draft, attachments, upload, read, … }) records everything the page sends
(sent, last_values()); upload and read say what the person picks and what a file holds.
In the browser: cliq hug dev <dir> [--params p.json] [--attachments dir] [--view answer.json] shows the hug on
cliqd’s local Hug page, rebuilding and reloading as you edit, with a panel of what it sends.
Building
cliq hug build runs the hug’s build (npm run build), writes hug.json and definition.mjs, and checks the
bundle: text files only (binary assets inlined), every file it loads in the bundle, at most 5 MB and 200
files, hug.json and definition.mjs present. CliqHub checks the same rules when the bundle is uploaded.
The scaffolded build.mjs passes hug_esbuild_plugins() from @getcliqio/hug-frontend/build to esbuild, for both
the page and definition.mjs. Today it keeps only zod’s English error messages — import { z } from 'zod' would
otherwise bring about 40 languages, some 500 KB, into each. Keep it in your build so later build fixes reach your hug
too; leave it out only if your hug switches zod to another language.