Conventions
Git workflow
- Push only to
main. Commit directly onmain; no feature branches or pull requests. - Conventional commits:
feat: …,fix: …,docs: …,feat(agent): …,fix(api): …. - Every push to
mainis deployed to production (Continuous deployment), and changes toentrosity-axis-agent/,entrosity-axis-connector/orentrosity-shared-go/also ship a dev build to agents (Agent releases). - Every code change updates this documentation in the same commit (Writing docs).
Code generation
Generated code is committed:
entrosity-axis.backend/internal/db/gen/(sqlc),entrosity-axis.backend/internal/http/gen/(oapi-codegen),src/api/schema.d.tsin each app (openapi-typescript, from the committedapi/openapi.yamlsnapshot),src/routeTree.gen.tsin each app (TanStack Router).
Never edit them. Change the .sql query, the migration or
entrosity-axis.backend/api/openapi.yaml and run make gen.
- Add a query to
entrosity-axis.backend/db/queries/<domain>.sqlwith a header such as-- name: ListDevicesByTenant :many. - Add an endpoint to
openapi.yamlfirst; the handler implements the generated interface. Then add its access rule inentrosity-axis.backend/internal/http/portal/access.goand a row in the RBAC matrix test.
Go
- Standard layout with
internal/packages; errors wrapped with%w; sentinel errors per domain (device.ErrNotFound); contexts everywhere; no global state except configuration. - Code used by more than one server (the Axis backend and Entrosity Hub)
lives in the
sharedmodule, with no database access:entrosity-shared-go/apperr(domain errors),entrosity-shared-go/reqctx(client IP, user agent, request ID),entrosity-shared-go/secretbox(AES-GCM secrets at rest with key rotation),entrosity-shared-go/authkit(argon2id passwords, password policy, opaque tokens, TOTP helpers, QR codes) andentrosity-shared-go/mailkit(SMTP and template rendering). Each server keeps its own database, migrations and e-mail templates. - Handlers translate domain errors to problem+json in one place
(
internal/http/errors.go); domain errors carry a stable code (apperr.New(kind, "code", "message"),entrosity-shared-go/apperr). New codes belong in Error codes. - Every tenant-scoped repository method takes
tenantIDas its first argument afterctx. - Every mutating service call records an audit entry in the same transaction.
Portal
- Feature folders:
src/features/<feature>/{api.ts, keys.ts, components/}; query keys inkeys.tsper feature. - No
any. Forms usereact-hook-form+zod. Route loaders prefetch withqueryClient.ensureQueryData. - Styling: Tailwind utility classes only, no inline
style, no CSS besides the theme tokens. - Charts use the shadcn/ui chart component (
@entrosity/ui/chart:ChartContainer,ChartTooltip+ChartTooltipContent,ChartLegend+ChartLegendContent) around Recharts primitives. Each series is declared in aChartConfigwith its label and colour, and drawn withvar(--color-<key>). Colours come from thechart-1…chart-10theme colours viachartColor(i)(src/lib/chart.ts), so charts follow light and dark mode. Series keys must be valid CSS identifiers (drive letters likeC:get a safe key). - Shared UI lives in
entrosity-ui(@entrosity/ui), used by every web app: the shadcn/ui primitives (@entrosity/ui/button, …), shared components (@entrosity/ui/components/DataTable,BrandLogo,ConfirmDialog,PageHeader, …),cn()(@entrosity/ui/lib/utils), the design tokens (@entrosity/ui/tokens.css, imported by the app'ssrc/index.css) and the Tailwind preset. Add a component there when a second app needs it; product-specific components stay in the app. Its README explains how to add a shadcn/ui primitive. - Statuses are shown with
@entrosity/ui/components/StatusBadge: one table (statusTones) gives every status its colour (good: green, busy: blue, warning: amber, bad: red, neutral: grey), the app passes the translated label, andtoneoverrides the colour where a status means something else. Add new statuses tostatusTones, not colours in the app. - Every app's header has
@entrosity/ui/components/BackButton, wired to the router'suseCanGoBack()androuter.history.back(). - Every user-visible text is translated (English, the default, and
Bulgarian). Texts live in message bundles next to the code
(
src/features/<feature>/messages.ts) made withdefineMessages({ en, bg })from@entrosity/ui/lib/i18n; the Bulgarian table must have exactly the English keys, so a missing translation fails the typecheck. Components read them withuseT(bundle)(@entrosity/ui/components/LocaleProvider), other code withtranslate(bundle, key)at call time, never at module load. Plurals usekey_one/key_otherwith a numericcount. Dates and numbers go through the helpers insrc/lib/format.ts, which follow the interface language. Terminology follows the Bulgarian documentation. The language choice is kept in localStorage (entrosity.locale), shared by the Hub and Axis; switching remounts the page. - Branding: the Entrosity mark and the product name "Entrosity Axis"
(
src/lib/brand.ts: the wordmark is "Entrosity" plus a muted "Axis"); primary colour Entrosity violet#7b35f5(accents#4c17b8). The web apps and this site use the hand-drawn look below.
Hand-drawn look (Ink & Strata)
Every web app (website, Hub, Axis, Edge, Matrix, Sphere) and this
documentation site follow the brand artwork: grey paper, wobbly black ink
outlines with irregular corners, violet wave strata sweeping in from the
corners, # and //// doodles and a soft glare. @entrosity/ui (0.6.0
and later) implements it; apps only pick the classes and components.
Tokens (tokens.css, HSL triplets used as hsl(var(--ink)); also
Tailwind colours ink and wave-1 … wave-6):
| Token | Meaning |
|---|---|
--background, --card | The paper and the lighter paper of panels |
--ink | The colour of drawn outlines (near black; chalk in dark mode) |
--shadow-ink | The hard offset shadows (ink; deep violet in dark mode) |
--wave-1 … --wave-6 | The strata, 1 deepest to 6 palest |
--wave-highlight | The highlight strokes on the waves |
--glare | The soft radial glare across the paper |
Dark mode is "ink-on-violet night": violet-black paper (#120d1f), chalk
ink and deeper strata. The font is Geist Variable. Tailwind's shadow-sm
… shadow-xl are hard ink offsets (shadow is 3px 3px 0), never blurs.
Classes (sketch.css):
| Class | Use |
|---|---|
sketch | A wobbly ink outline drawn by ::before; keep the element's border class (transparent, for layout). Makes the element position: relative |
sketch-sm, sketch-lg | Corner sets for small controls and chips, and for big panels |
sketch-double | A second, fainter offset stroke (::after), for cards and dialogs |
sketch-field | Inputs, textareas and selects: a real 1.5px ink border with the irregular corners |
sketch-shape | Only the irregular corners, for hover and selected fills and code blocks |
sketch-edge-r, -l, -t, -b | A single hand-drawn ink edge (sidebars, headers); a background line, so it stays on scrolling panels |
sketch-panel | A real hand-drawn ink border for panels that scroll (dialogs) |
sketch-underline | A hand-drawn squiggle under inline text; sketch-underline-primary draws it in violet, sketch-underline-hover shows it only on hover and focus |
hatch-grid, hatch-tally | The # and //// doodles, in ink colour |
paper | The page background: paper plus the glare |
Components:
SketchWaves(@entrosity/ui/components/SketchWaves): the strata out of a corner,corner="tr|tl|br|bl",bands3–6,highlights. It fills the boxclassNamegives it and is decorative (aria-hidden).HatchMark: one doodle,variant="grid|tally", sized and placed byclassName.SketchFrame: the artwork as a container (paper, double outline, waves in the top-right and bottom-left corners, hatches), for sign-in pages and heroes;waves="sm|md|lg",hatches,contentClassName.SketchDefs: the SVG filters that make the outlines wobble; it also putssketch-readyon<html>, which turns the wobble on (without it the outlines are straight).ThemeProvidermounts it; mount it yourself only withoutThemeProvider.BrandLogowithunderlinedraws the hand-drawn line under the wordmark.SketchIllustration(0.7.0): spot illustrations in the artwork's hand,name="empty|not-found|error|search|devices|shield|camera|network|done"; decorative unless given atitle.EmptyState(0.7.0): an illustration, title, description and action for empty lists, no results and 404 pages;compactinside cards.DatePicker(@entrosity/ui/date-picker, 0.7.0): a hand-drawn calendar in a popover, withwithTimefor date and time. It reads and writes the same strings as<input type="date">andtype="datetime-local", and replaces them in every app. Plaintype="time"inputs stay native.- Charts in
ChartContainerare drawn in the same hand (ink-outlined bars and slices, wobbly lines, pencil gridlines); the palette starts with the strata violet (--chart-1), then green, amber, blue and red.
The library's Button, Card, Dialog, Sheet, Popover, DropdownMenu, Select, Tabs, Tooltip, Toast, Badge, StatusBadge, Table, DataTable, PageHeader and form controls already use the look.
Rules:
- Never put
sketchon<input>,<textarea>,<select>or<img>: they cannot have::before. Usesketch-fieldfor fields; leave images alone or outline their container. - Do not combine
sketch,sketch-fieldorsketch-shapewithrounded-*: the utility replaces the irregular corners. Round dots (rounded-full) stay as they are. - Shadows are hard ink offsets (
shadow-sm,shadow, …); do not add blurred or coloured shadows. - Ad-hoc panels are
sketch border …(sketch-doublefor big cards,sketch-smfor chips), notrounded-md border …. - Every page works from 320px wide up: no horizontal page scroll, wide tables scroll in their own box, filters and forms stack on phones, grids drop to one column, dialogs fit the screen and scroll inside.
- Empty lists, no results and 404 pages use
EmptyState; date fields useDatePicker. - The wobble filter is opt-in (
sketch-wobble, @entrosity/ui 0.7.2) and only for one-off pieces such asSketchFrame: an SVG filter on every control, chart or list row re-renders on each repaint and makes busy pages lag. Nobackdrop-bluron sticky headers or bars for the same reason. - Waves and hatches are decoration: no text on the waves without a solid background, and keep focus rings. In Windows high contrast mode the drawn ink is hidden and plain borders are shown.
- This site copies the tokens and rules into
src/css/custom.cssand the filters intosrc/theme/Root.tsx(it does not depend on@entrosity/ui); the homepage's waves arestatic/img/waves.svgandwaves-dark.svg. Keep them in step with the library.