Materializr
Open-source parametric 3D CAD for makers — constraint sketches, solid modeling, threads, SVG & text engraving, STL/STEP export. Android, iPad, Linux, Mac, Windows. Vibe coded.
翻译准备中
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
An htmx-native design system. The polish of the modern component
aesthetic — complete states, disciplined spacing, dark as a material —
with zero client framework. Server-rendered markup, semantic classes,
OKLCH design tokens, and interactions built on the htmx request
lifecycle.
Browse the live gallery →
Every example is its copy-paste snippet — the demo and the docs are the
same string, so they cannot drift.
Hosted on the jsDelivr free CDN, pinned to a
release tag (fonts resolve relatively — no extra setup):查看完整 README ↗An htmx-native design system. The polish of the modern component aesthetic — complete states, disciplined spacing, dark as a material — with zero client framework. Server-rendered markup, semantic classes, OKLCH design tokens, and interactions built on the htmx request lifecycle.
Browse the live gallery → Every example is its copy-paste snippet — the demo and the docs are the same string, so they cannot drift.
Hosted on the jsDelivr free CDN, pinned to a release tag (fonts resolve relatively — no extra setup):
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/manwithacat/[email protected]/dist/hatchi-maxchi.css"
integrity="sha384-SPOTkEk27jv24h/Dpih9OwXM0dTGCoEf0uQQJEHse8P9E8qhdk+QvPsut3A6Wur3"
crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/gh/manwithacat/[email protected]/dist/hatchi-maxchi.js"
integrity="sha384-M9W0grtfNiYrivGnVK/FRI4+YNlf3x1RkyXuQT8byKKvOIRyWt3z7JABBMfTdZk6"
crossorigin="anonymous" defer></script>
Each release's
notes carry the ready-to-paste snippet with that version's SRI hashes
(a CI drift gate pins the committed dist/ to the sources, so the tag
serves exactly these bytes).
Prefer self-hosting? Download the same two files (plus fonts/) from the
latest release
or build them yourself (python build.py):
<link rel="stylesheet" href="hatchi-maxchi.css">
<script src="hatchi-maxchi.js" defer></script>
Then copy any component's HTML from the gallery and point its hx-*
attributes at your endpoints:
<button class="button button-primary">Save changes</button>
<span class="badge" data-tone="success" role="status">Approved</span>
<!-- the command palette is an hx-get endpoint, not a client fuzzy-finder -->
<dialog class="command" aria-label="Command palette">
<input class="command__input" type="search"
hx-get="/app/command" hx-trigger="input changed delay:150ms"
hx-target="next .command__results">
<div class="command__results" role="listbox"></div>
</dialog>
No build step, no JSX, no utility soup. One semantic root class per
component, data-* attributes for variants, tokens for every value.
A React component bundles structure, behaviour, and client state into a tree. HaTchi-MaXchi's unit is different enough to deserve its own name — a Hyperpart:
Hyperpart = a partial (server-rendered markup + classes) + its exchange contract(s) (the endpoint request/response the server must satisfy) + an optional controller (vanilla JS, only where the platform lacks a primitive).
The naming is deliberate. "Component" imports React priors — client state, props, composition trees — which are exactly wrong for a server-owned partial, and an agent told to build a "component" reaches for them. A Hyperpart has no client state graph: state lives on the server and htmx swaps the markup.
The second half — the exchange contract — is the part shadcn never had
to standardise (React resolves state locally). For interactive Hyperparts
the gallery documents it explicitly: what request the affordance makes,
and what fragment your server must return. It's a first-class, CI-checked
artifact (a partial that makes an undeclared request fails the build). In
Dazzle the response is emitted automatically; in any other htmx app, return
matching markup from your endpoint. (Vocabulary from Hypermedia Systems:
a request/response round-trip is a "hypermedia exchange"; the individual
hx-* control that initiates it is an "affordance".)
If you come from React / SPA land, you already have tools for half of this problem — just not under one name, and not usually aimed at HTML as the wire format.
| You're used to… | Rough HM analogue |
|---|---|
| Zod / Yup / PropTypes — "this value matches a schema" | Schema half of a dual-lock: a Pydantic model for host-trusted props (when the part has real data, not only a root) |
Testing Library / Playwright — getByRole, data-testid asserts | DOM half: DOM_CONTRACT — root selector + required attrs (data-dz-*, stable classes) checked against emitted HTML |
| TS props on a component | "What the host may put in" — but enforced on server-rendered markup, not a client component instance |
| Storybook + Chromatic | Gallery exemplars + optional visual smoke; dual-lock is the structural CI gate, not the pixel judge |
| OpenAPI for endpoints | Closer to the exchange contract above (request/response + exchange envelope). Dual-lock is the partial side; the swap contract is the topology side |
A dual-lock is the pair of gates that freeze one Hyperpart so design system and host (e.g. Dazzle) cannot silently drift:
contracts/<part>.py → DOM_CONTRACT) — the markup shape
is machine-checkable. Exemplars and host emissions must validate_dom.render()) — the data shape that
produces that markup. Present when there is a real emit path; absent on
many gallery substrates (DOM-only dual-locks)."Dual" is load-bearing only when both halves exist (schema+DOM). A DOM-only dual-lock is still a first-class CI contract on the stable root — think "public DOM API for this part" — not a half-finished Zod schema.
Why invent a term? Neither half is novel. The pairing is what SPA
mental models under-name: in React the component owns the DOM, so a props
schema is enough; the tree re-derives markup. In hypermedia, the fragment
is the API — clients swap HTML, they do not re-render from a client state
graph. Freezing data shape + markup shape (or at least the stable root)
is how multi-host design systems and coding agents stay honest under
velocity. Inventory: DUAL_LOCK_COVERAGE.md;
authoring path: contracts/AUTHORING.md.
One sentence for a SPA colleague:
Zod contracts JSON. A dual-lock contracts a Hyperpart's HTML (and, when present, the model that renders it) — both checked in CI.
A Hyperpart's code is physically scattered by build necessity: its CSS
is concatenated in cascade-layer order (so it lives in components/*.css
accumulators), its controller is bundled (controllers/), and its markup
and contract are in site/registry.py. We can't co-locate everything in one
file like shadcn — but the relationship must stay legible, both to an agent
and a human. Two mechanisms, kept honest by a gate:
partial and
exchanges inline, controller and mock as pointers./* HYPERPART: <id> */ marker, so opening any file tells you which
Hyperpart it serves. Styles are discovered from these markers (a
component's CSS is genuinely many-to-many across files), not hand-listed.python tools/hyperpart.py <id> assembles the full anatomy (including who
embeds / refuses this part):
$ python tools/hyperpart.py command
Hyperpart: command (Command palette)
partial site/registry.py (inline markup)
exchanges GET /app/command
styles components/hm-core.css:256
controller controllers/dz-command.js
mock /mock/command
For coding agents: start at AGENTS.md (curriculum, stems,
layers, invention ladder). Procedures: docs/agent/. Constitution:
docs/decisions/. Do not treat the gallery mock as the product API.
Consistency tools (committed + CI drift gates):
| Tool | Output | Purpose |
|---|---|---|
tools/consumer_map.py --write | CONSUMER_MAP.md | Reverse composition index + explicit non-compositions |
tools/contract_surface.py --write | CONTRACT_SURFACE.md | DOM/model surface breaking-change detector |
tools/dual_lock_coverage.py --write | DUAL_LOCK_COVERAGE.md | Dual-lock inventory (schema+DOM vs DOM-only) — see Dual-locks |
Gallery Pages (site/build_site.py) | site/hyperparts/*.html, site/guide.html | Human-facing Server exchange + Swap contract (envelope column); rebuild after registry envelope changes |
tests/test_hyperpart_cohesion.py fails CI if the manifest and the markers
disagree (unowned controller, orphan marker, interactive Hyperpart with no
style marker, controller not marked). The gallery shows the same anatomy
inline for interactive Hyperparts.
Most design systems assume a client framework owns the DOM. HaTchi-MaXchi assumes the server owns the DOM and htmx swaps it:
hx-get — results are a server
swap, scoped to the signed-in user, not a client-side index.hx-confirm gets the designed
dialog automatically (dz-confirm.js intercepts htmx:confirm) — no
per-button wiring.htmx-request, swap events), so states are complete by construction.<details>/<dialog> — native behaviour,
styled; JavaScript only where the platform has no primitive.All colour flows through OKLCH semantic tokens; light/dark is
light-dark() bound to [data-theme] (with prefers-color-scheme as
the default). Rebrand by overriding tokens — never by editing components:
:root {
--colour-brand: oklch(54% 0.2 150); /* your accent */
--radius-md: 0.5rem; /* your geometry */
}
Key tokens: --colour-bg, --colour-surface, --colour-surface-muted,
--colour-text, --colour-text-muted, --colour-border,
--colour-brand, --colour-success/warning/danger/info, the
--colour-*-soft washes, and the --focus-ring-* family. See
tokens/tokens.css — it reads top to bottom.
HM ships unprefixed: .button, .badge, data-tone,
@keyframes fade-in. Clean, readable markup you can copy straight from the
gallery. Add your own namespace at build time if you want one:
python build.py # default: unprefixed (.button, data-tone)
python build.py --prefix dz- # .dz-button, data-dz-tone, dz-fade-in
python build.py --prefix ax- # any lowercase namespace
The rename is total across CSS, JS, and the demo markup (class names,
data-* attributes, keyframes) — plus the public custom-property
knobs you set yourself (--progress-value, --list-rows,
--touch-target-min in the default build; declared in build.py's
PUBLIC_CSS_PROPS). Internal --* tokens keep their names — they're
private. Your markup must match the prefix you build with.
This isn't a toggle bolted on for flexibility — it's a production path.
Dazzle consumes HM by building it
with --prefix dz- at ingest, so every Dazzle release exercises the
transform end-to-end (and if it ever broke, Dazzle's whole UI would break —
a much louder gate than any unit test). HM ships clean; the consumer
namespaces.
| Path | What lives here |
|---|---|
tokens/ | tokens.css — OKLCH ramps + semantic tokens (--colour-*), type, spacing, shadow, motion, focus. The single colour vocabulary. |
base/ | base.css (element defaults + focus net), fonts.css (Geist @font-face), design-system.css (non-colour tokens + vendor-widget theming). |
components/ | Component CSS. One semantic root class + data-dz-* modifiers; tokens carry the aesthetic. |
controllers/ | Vanilla-JS for the purely-client bits (dz-confirm.js, dz-command.js). htmx-aware, framework-free. |
icons/ | The Lucide subset — source of truth (registry.py, ISC), rendering helpers, and the generator. Dazzle vendors a drift-gated copy. |
site/ | The gallery — a committed build artifact, deployed to Pages as-is. registry.py is the source of truth for the Hyperpart catalogue (partials + their exchange contracts). |
build.py | Builds dist/ (CSS + JS + fonts) from package sources. Stdlib-only; takes --prefix. |
tests/ | Regression gates: contract (console), behaviour (headless Chromium), visual (screenshot vs committed per-platform baselines), and WCAG 2.2 AA (vendored axe-core over the gallery in light + dark + opened-overlay states; allowlist may only shrink). |
HaTchi-MaXchi matches shadcn's quality signals (complete states, spacing discipline, focus rings, layered elevation, dark-as-material) but owns its identity signals — a chromatic accent (not monochrome zinc), colour+icon+text semantics (WCAG 1.4.1: status never relies on colour alone), tone-wash surfaces, htmx-lifecycle motion, and ops-app data density. The review test for every component: credible to a shadcn-fluent developer, but not mistakable for shadcn side-by-side.
Semantic versioning from 0.1.0; package.json is the version source
of truth and release tags (v0.2.0) must match it — CI enforces this and
attaches the built bundle to each
GitHub release.
Pre-1.0, minor bumps may rename or reshape components; patch bumps are
safe to restyle on.
The system is developed inside the
Dazzle monorepo
(packages/hatchi-maxchi/ — the source of truth). PRs against this repo
are welcome for discussion, but fixes land in the monorepo, where the
design system's blind-panel taste oracle and a 12-app example fleet
exercise every component.
Publish pipeline: every monorepo push touching the package triggers a
subtree sync to this repo's main (Dazzle's sync-hatchi-maxchi.yml,
authenticated by a repo-scoped deploy key); this repo's CI then gates the
sync and Pages redeploys the gallery. Releases are never automatic:
bump package.json in the monorepo (the sync carries it here), then
either push the matching v* tag or run the Release workflow from the
Actions tab with the version input — both validate tag == package.json
and attach the built bundle + CDN/SRI snippet.
Run the gates locally:
pip install pytest playwright pillow && playwright install chromium
python build.py && python -m pytest tests/
Visual baselines: CI compares tests/baselines/linux/ only. That set is
committed as real blobs (the standalone repo has no Git LFS). After an
intended visual change, capture Linux through update-baselines.yml and
commit tests/baselines/linux/ including capture.json. A local run on
another platform skips when its baseline is absent and does not write.
HM_UPDATE_BASELINES=1 captures on purpose; baselines/darwin/ is
gitignored.
Hyperparts must work under a finger, not just a mouse. There is no
separate "mobile version": the same partial adapts via @media (pointer: coarse) (the explicit "primary input is a finger" signal — width queries
misclassify touch tablets in landscape and mouse users in small windows).
Checklist for any interactive Hyperpart or extension:
components/touch-targets.css covers the button family; new
affordances add their own rule). Edge affordances (e.g. the grid's
resize handle) may use a widened strip instead of a full 44px block
when a block would swallow the neighbouring control — document the
trade-off at the rule.:hover must be
visible (or reachable another way) on coarse pointers — hover does not
exist under a finger.touch-action: none
on the handle (else scroll panning swallows the pointermove stream);
a tap/double-tap affordance needs touch-action: manipulation (else
the double-tap-zoom heuristic eats the second tap). Controllers use
Pointer Events, which unify mouse/touch/pen — never mousedown.is_mobile + has_touch context (flips
pointer: coarse) asserting the coarse-pointer CSS applied and the
pointer contract works with pointerType: 'touch' synthetic events.
Synthetic events pin the controller contract; gesture arbitration
(pan vs drag) can only be proven on a real device — note it in the
test docstring, and verify on iPad when the affordance is new.components/ — one root class, data-dz-* variants,
tokens for all values.Hyperpart(...) to site/registry.py — its partial is the
gallery demo and the copy-paste snippet (same string, can't drift).Exchange(...) per request in
the Hyperpart's exchanges — method, endpoint, trigger, response
fragment, swap. The contract gate fails if the markup makes a request
with no matching Exchange (or declares one it doesn't make).controllers/.build.py CSS_SOURCES (and, in-tree,
Dazzle's build lists). Rebuild; the contract test fails on any
published class without a rule.MIT. Vendored assets: Geist (OFL, see
site/fonts/OFL.txt) and Lucide icons (ISC).