Skip to main content

Conventions

Git workflow​

  • Push only to main. Commit directly on main; no feature branches or pull requests.
  • Conventional commits: feat: …, fix: …, docs: …, feat(agent): …, fix(api): ….
  • Every push to main is deployed to production (Continuous deployment), and changes to entrosity-axis-agent/, entrosity-axis-connector/ or entrosity-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.ts in each app (openapi-typescript, from the committed api/openapi.yaml snapshot),
  • src/routeTree.gen.ts in 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>.sql with a header such as -- name: ListDevicesByTenant :many.
  • Add an endpoint to openapi.yaml first; the handler implements the generated interface. Then add its access rule in entrosity-axis.backend/internal/http/portal/access.go and 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 shared module, 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) and entrosity-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 tenantID as its first argument after ctx.
  • 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 in keys.ts per feature.
  • No any. Forms use react-hook-form + zod. Route loaders prefetch with queryClient.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 a ChartConfig with its label and colour, and drawn with var(--color-<key>). Colours come from the chart-1 … chart-10 theme colours via chartColor(i) (src/lib/chart.ts), so charts follow light and dark mode. Series keys must be valid CSS identifiers (drive letters like C: 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's src/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, and tone overrides the colour where a status means something else. Add new statuses to statusTones, not colours in the app.
  • Every app's header has @entrosity/ui/components/BackButton, wired to the router's useCanGoBack() and router.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 with defineMessages({ 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 with useT(bundle) (@entrosity/ui/components/LocaleProvider), other code with translate(bundle, key) at call time, never at module load. Plurals use key_one / key_other with a numeric count. Dates and numbers go through the helpers in src/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):

TokenMeaning
--background, --cardThe paper and the lighter paper of panels
--inkThe colour of drawn outlines (near black; chalk in dark mode)
--shadow-inkThe hard offset shadows (ink; deep violet in dark mode)
--wave-1 … --wave-6The strata, 1 deepest to 6 palest
--wave-highlightThe highlight strokes on the waves
--glareThe 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):

ClassUse
sketchA wobbly ink outline drawn by ::before; keep the element's border class (transparent, for layout). Makes the element position: relative
sketch-sm, sketch-lgCorner sets for small controls and chips, and for big panels
sketch-doubleA second, fainter offset stroke (::after), for cards and dialogs
sketch-fieldInputs, textareas and selects: a real 1.5px ink border with the irregular corners
sketch-shapeOnly the irregular corners, for hover and selected fills and code blocks
sketch-edge-r, -l, -t, -bA single hand-drawn ink edge (sidebars, headers); a background line, so it stays on scrolling panels
sketch-panelA real hand-drawn ink border for panels that scroll (dialogs)
sketch-underlineA 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-tallyThe # and //// doodles, in ink colour
paperThe page background: paper plus the glare

Components:

  • SketchWaves (@entrosity/ui/components/SketchWaves): the strata out of a corner, corner="tr|tl|br|bl", bands 3–6, highlights. It fills the box className gives it and is decorative (aria-hidden).
  • HatchMark: one doodle, variant="grid|tally", sized and placed by className.
  • 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 puts sketch-ready on <html>, which turns the wobble on (without it the outlines are straight). ThemeProvider mounts it; mount it yourself only without ThemeProvider.
  • BrandLogo with underline draws 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 a title.
  • EmptyState (0.7.0): an illustration, title, description and action for empty lists, no results and 404 pages; compact inside cards.
  • DatePicker (@entrosity/ui/date-picker, 0.7.0): a hand-drawn calendar in a popover, with withTime for date and time. It reads and writes the same strings as <input type="date"> and type="datetime-local", and replaces them in every app. Plain type="time" inputs stay native.
  • Charts in ChartContainer are 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 sketch on <input>, <textarea>, <select> or <img>: they cannot have ::before. Use sketch-field for fields; leave images alone or outline their container.
  • Do not combine sketch, sketch-field or sketch-shape with rounded-*: 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-double for big cards, sketch-sm for chips), not rounded-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 use DatePicker.
  • The wobble filter is opt-in (sketch-wobble, @entrosity/ui 0.7.2) and only for one-off pieces such as SketchFrame: an SVG filter on every control, chart or list row re-renders on each repaint and makes busy pages lag. No backdrop-blur on 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.css and the filters into src/theme/Root.tsx (it does not depend on @entrosity/ui); the homepage's waves are static/img/waves.svg and waves-dark.svg. Keep them in step with the library.