Traduction incomplète

Cette page n'est pas encore entièrement traduite : les passages non traduits sont affichés en anglais.

0017. Visual system, brand and application shell

  • Status: accepted

  • Date: 2026-11-07

  • Supersedes: the font and colour clauses of 0009

Context

The first interface lagged behind the v2 mock-up the maintainer had validated: no logo, system fonts, a header that looked like a prototype, a sign-in page loaded with options. The business screens of the next milestone build on whatever is laid down now, so the visual foundation comes first.

Decision

Name. The name shown to a human is HouseCairn (interface, page titles, manifest, one-time-password issuer, API documentation, README, contributing guide). Technical identifiers stay lower case: housecairn (package, image, HOUSECAIRN_* variables, addresses). NOTICE and CLA.md are legal texts and are not edited in this change.

Tokens. design/tokens.json stays the single source and now carries the mock-up values: the palette (slate, sand, sage, stone, soft tints, status colours), the Manrope and JetBrains Mono stacks, a type scale, spacing, radii (10, 12, 14, 22 px), two shadows and a layout group (44 px touch target, content width, navigation width). Names follow the existing --hc-<group>-<name> scheme.

Fonts. Manrope (text) and JetBrains Mono (code) are self-hosted as Latin-subset variable WOFF2 files under static/fonts/, with their SIL OFL 1.1 texts, declared in static/css/fonts.css with font-display: swap. A test pins their SHA-256. Nothing is requested from another host (0011).

Deviations from the mock-up, all for WCAG 2.2 AA. A test computes the contrast of every text and component pair of the tokens (4.5:1 for text, 3:1 for borders of controls and the focus ring):

  • muted text #4F605C instead of #66736F (4.1:1 on the tinted backgrounds);

  • control borders use border-strong #7A8783 (3.7:1) instead of the decorative hairline #E3DED6 (1.3:1), which stays for cards and separators;

  • overdue and error text #9A3F1D instead of #B4532F (4.0:1 on its tint);

  • the focus ring is slate, 3 px, offset 2 px, instead of sand (2.3:1 on white).

Theme. Light only: the mock-up has no dark theme. Adding one later means a second set of token values, not new components.

Shell. One <nav> with three entries (Home, Homes, More), rendered once and laid out by CSS: a tab bar fixed at the bottom on a phone (48 px targets, icon and label, safe-area padding), a column on the left from 56 rem. The app bar carries the brand and an empty header_actions block (the documentation link goes there). Signed-out pages have no navigation: the wordmark centred above one panel. The page title is the h1 of the content, not a second heading in the bar. Entries after the daily ones live on the More page, as in the mock-up.

Components. Plain CSS in static/css/app.css, no framework: cards, lists and rows, buttons (primary, secondary, sand, danger, icon), fields, badges (pills), alerts and messages, a segmented control, chips, code blocks. Django-allauth renders plain tags; its element templates (button, provider, alert, badge, panel) are overridden under templates/allauth/elements/ so its pages share the look. Icons are inline SVG from templates/components/icon.html. No inline style and no inline script anywhere.

Files. static/css/app.css is the entry point: it only imports, from the same directory and in this order, base.css (reset, typography, focus), shell.css (app bar, navigation, content area, signed-out layout), components.css and utilities.css (rules over everything else, such as reduced motion), after the tokens.css and fonts.css that base.html loads first. No build step: the static files storage rewrites each @import to the hashed file name. The order is the original cascade order, shell before components, because some components refine a shell rule of the same specificity (.chevron after .icon). The shell’s markup is in templates/base.html, which includes templates/shell/brand.html, help_link.html and navigation.html (the tab bar on a phone and the column on a computer are the same nav); the header_actions, main_class and content blocks stay in base.html, since a block inside an included template cannot be overridden.

Brand files. Sources in design/brand/ (with the trademark note), served copies in static/img/brand/: wordmark, icon, favicons, apple-touch-icon, 192 and 512 px icons. The web manifest is a view (/manifest.webmanifest) because the icon addresses carry a content hash in production.

Errors. 400, 403, 403_csrf, 404 extend the shell; 500 is standalone (no session, no database, no form) because it is shown when the application failed.

Evidence for review. The workflow ui.yml (not a required check) starts a throwaway instance, takes screenshots of the main screens on a phone and a desktop in English and French, and attaches them to the run. Playwright is installed for that job only, from the docs dependency group locked in uv.lock (0016); it is not a dependency of the application. The documentation job keeps its own screenshots (the pages of the site) as an artifact too.

Screens of the mock-up not built yet. Household, API tokens, notifications, integrations, data, onboarding, and every business screen: they do not exist in the application yet and arrive with their own features. Screens that do exist (home, homes, a home, more, about, sign-in, sign-up, password reset, second factor, passkeys, security keys, recovery codes, e-mail, password, third-party connections, errors) use the shell.

Consequences

  • A colour or size change is a JSON edit plus the regenerated CSS; the contrast test fails if it breaks AA.

  • Two font files (about 65 kB) are part of the image and cached for a year.

  • The v2 mock-up is the reference for every later screen; a deviation is recorded here or in a later record, with its reason.