The interface is grey, flat and has no system: give it tokens, colour, a second plane, and density as a preference #292
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#292
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Postulo's interface is correct and it is plain. Every page passes axe in both themes, reflows at 320 in three languages, survives the text-spacing override, keeps its focus rings under Windows High Contrast and prints as ink on white — #273 to #278 saw to all of it. None of that made it good to look at, and none of it was ever supposed to: conformance is a floor, not a design.
This issue is the part that is not framework adoption. #290 and #291 bring Basecoat's components in; this is about what they are painted with, and it is where the application would actually start to look like something somebody chose.
What the interface is, measured
It is grey. Counting every colour utility written in the 184 templates:
ink(the grey scale)brand(indigo)amberredemeraldNine in ten colour decisions are a shade of grey, and every colour that is not the brand appears only to raise an alarm — amber for a warning, red for an error, emerald for a success. Colour is never used for structure, for grouping, for telling one kind of thing from another, or for pleasure. That is not an accessibility requirement: the criteria constrain which colours may carry meaning and at what contrast, never whether an interface may have any.
It is flat. Two
shadow-lgin 184 templates, and.cardcarriesshadow-sm(assets/css/app.css:456). No gradients anywhere. There is exactly one plane, so nothing is above anything else — a popover, a card and the page are all the same distance from the reader.It has no system to be consistent with.
assets/css/app.cssdefines one@themeblock (:69-129) holding a font stack, an 11-step ink scale, a 10-step brand scale, and Basecoat's semantic aliases. There is no radius token, no shadow token, no spacing token and no type-scale token — none, anywhere. Basecoat'sbase.csswould normally supply them and #262 deliberately did not import it, for reasons that were right about thedarkvariant and the fonts and said nothing about radii.So every template decides for itself, and they disagree — measurably, at every level:
mb-1 text-2xl font-semibold tracking-tight(43), the same without the margin (24),text-2xl font-semibold tabular-nums(12),mb-6 …(9),mb-4 …(1),mb-2 …(1).<h2>carriesmb-3 font-medium40 times,mb-4 font-medium19, barefont-medium16,mb-1 font-medium13,mb-2 font-medium11, and six more.api/token_list.htmlusesmb-4at one card andmb-3at another. An<h3>is sometimes smaller than its<h2>and sometimes the same size.gap-3(30),gap-2(7),flex-wrap gap-2(6),gap-1(5),flex-wrap gap-1(5), with no rule tying the gap to anything.items-center gap-4(16),items-start gap-4(8),items-start justify-between gap-4(2) — and inside it the title column isgrow basis-48nine times and a bare<div>otherwise, while the action is a wrapperms-autofifteen times andclass="btn ms-auto"on the button itself nine times. Four list pages, four structures, one intended result.<dt>beside a value is at three different sizes depending on which page you are on.applications/report.htmlrepeats the same<dt>/<dd class="text-2xl font-semibold tabular-nums">tile nine times inline, and the dashboard widgets draw the same idea again separately.Rounding is
rounded-full(20),rounded-lg(11),rounded-xl(2),rounded-md(1) in templates, plus whatever each class inapp.csschose —.cardisrounded-xl,.btnisrounded-lg,.field-inputis its own. There is no rule, because there is no token to be a rule.Colour it already has and does not use.
app.css:384-410defines seven tag colours — grey, blue, amber, violet, teal, green, rose, each tuned to 3:1 against the card in both themes. One of them is used anywhere (tag-grey, once). The other six are dead code waiting for #285, which asks for exactly this. The palette exists; nothing spends it.There is no motion. One transition in the whole stylesheet, on the funnel bar, and it is opted into behind
prefers-reduced-motion: no-preference(app.css:773-777) — which is the right shape and the only instance of it.And nothing is written down. There is no design page in the wiki's 23, and
CONTRIBUTING.mddescribes the mechanics — components live intemplates/cotton/, Basecoat draws them, icons come from Lucide — without a word about what the interface should look like. There is no gallery page where a change could be seen.What to do
1. Give the system its missing tokens. A
--radiusand a rounding scale derived from it; two or three elevation tokens rather than oneshadow-smand two strays; a type scale and a spacing rhythm named once. These belong in the@themeblock beside the colours, and once they exist the six ways of writing a page title become one component.2. Spend the colour that is already there. The brand scale has ten steps and the interface uses a handful. Section accents, the current state of things, the funnel and the report's chart, the tag palette that #285 will light up, the kind-chips on the plugin pages: all of these can carry colour that means something, at the contrast the tests already enforce.
3. Give the interface a second plane. Cards raised off the page, popovers and menus above cards, the sticky header with a real edge when the page is scrolled under it. Three levels is enough and one is not.
4. Fill the states that are missing. Basecoat's
emptyandskeletonneed no script and Postulo has neither: a list with nothing in it is a bordered box with a sentence, and a slow page is a spinner. Those two are the largest "unfinished" impressions in the application.5. Make density a preference, not a compromise. This is the part the per-user machinery now makes possible. A comfortable default and a compact option, chosen per account — the profile already carries
theme,hidden_nav_items,show_career_orderandkeyboard_shortcuts, and #281 has just given the preferences a section of their own. A person who wants more rows on a screen asks for them; the default stays generous. The same mechanism answers #289's underline.6. Write it down, and make it visible. A short design page in the wiki saying what the tokens are and when to use each, and a gallery page in the application showing every component in both themes — which is also the page a browser test should walk, because a component that is only ever seen inside a feature is a component nobody reviews.
What may not be traded for it
The floor is tested, in detail, and the redesign has to land on top of all of it:
IGNORED_RULESempty — not one rule is excused today and none should be.tests/test_contrast.py, not from screenshots: the field boundary at 3:1 against both the field and the page, in both themes, and the focus ring opaque. Its parser reads the@themeblock positionally and understands only literaloklch(L C H)values, so a token written any other way, or the dark block moved, makes it silently stop checking. If the tokens are restructured, that parser must be fixed in the same commit, not afterwards.style-src 'self'with nounsafe-inline, enforced in the browser suite. Nostyleattribute and no<style>element — so nothing in this may be implemented by writing a value into markup. The stored column widths already had to be applied through the CSSOM for this reason.Points to settle
Notes for whoever takes it
npm run build:cssand the compiled stylesheet committed, every time;tests/test_stylesheet.pycompares them byte for byte.tests/test_template_lint.pyforbids physical sides (ml-,pl-,text-left,left-*,border-l,rounded-l) in templates and in both stylesheets. A new rounding or spacing scale must be logical throughout.fr-fr,pt-pt,pt-brasdraft.Five of the six parts are on
main; two pieces are left and both are per-surface work rather than one pass.Landed
44c6e3a77abbbfed04--radiuswith the scale derived from it; three planes; the masthead's edge69c01b9c7a013e5bb83ae59b2d5c2ad54b60+ wiki28c72a1Two decisions taken along the way
No skeleton, deliberately. This issue asks for one on the grounds that a slow page is a spinner. It is not: #226 already settled what a page does while htmx replaces part of it -- opacity, a cursor, no movement, because what is under it is the previous answer and still true. A skeleton belongs where there is nothing yet, and Postulo renders on the server, so there is no such moment. Importing it would be dead code or a contradiction.
The rounding values did not change. The scale is derived from one token and computes to Tailwind's own values to the pixel, so no corner in the application moved. The commit is the rule, not a restyle; the gallery is there for whoever changes the base.
What is left
Spend the colour. Untouched. The tag palette is now visible in the gallery but nothing spends it -- that is #285's job and is better done there. The report's chart, the funnel and section accents are still grey and are the honest remainder of this item.
The heading components. A page title is still written six ways and a section heading eleven. The component looked like a mechanical pass and is not: seventeen pages put the title inside a flex header with the actions beside it, where a wrapper with its own margin breaks the layout, and the rest are standalone. A component that fits both shapes needs per-page judgement, which is what this issue means by "a surface at a time". A half-written one was started and removed rather than left as a component that does not fit its call sites.
Both are separable and neither blocks the others. Worth deciding whether they stay here or become issues of their own.