UI, design and accessibility
55 lessons. Most-repeated first.
- First baseline findings (Nov 2026): Cairn 106 open issues (A4 muted nav labels 3.33:1, A5 28 px buttons, A6 10.5 px labels), Glow 18 (A4, A5 28 px pills); Cairn’s app accent #d9481f vs landing teal #0f766e (ΔE 101) → brand sync candidate.
- Poster/editorial heroes hide stickers and show a large emblem; center/split show a small emblem above the pill.
.hero.split.rev .stats needs order: 3 or stats jump above the copy (fixed in design.py; keep it).
- Layout changes: check 320, 390, 768, 1024 and 1440 px; keep only if better at every width.
- The first winner overflowed to 441 px on a 390 px phone (long URL in a grid cell plus an unwrapped
<pre>). Use minmax(0,1fr) columns, pre-wrap code and <wbr> after URL punctuation. bracket.py treats overflow as a blocker.
- Fonts.css generated next to the font files must use url(file.woff2), not url(fonts/file.woff2); the bracket ran on fallback serif until fixed. Eyeball the first screenshots before judging.
- A frozen rubric that names a colour (“blue-violet accent”) fights a later user ban (“no purple”); judges dock the plain-blue build on identity. Record the conflict instead of quietly re-adding the colour. A white primary button also lost on identity; a solid accent CTA won.
#3b82f6 on the dark panel failed A4 contrast as text; use a lighter accent tint for text and keep the saturated accent for fills.
- The gallery
p/index.html is one long grid line, so two sessions adding cards always conflict. Resolve at card level: keep upstream’s grid and insert your <article> after the same neighbour it had in your build (split both sides on <article>...</article>, match on <h3>). You can also render the gallery from a filtered state to leave out unpublished slugs.
- Phone check for any app: assert document.documentElement.scrollWidth == viewport width on every view at 320 and 390px (compare/check_mint_phone_overflow.py). One wide table makes mobile browsers widen the layout viewport, and every fixed bottom sheet then runs off-screen; wrap wide tables in their own overflow-x box and use minmax(min(Npx,100%),1fr) for grids.
- Vow run 4: v9 sits at a plateau of about 8.0-8.1 on ui-v1. Layout reshuffles (moving the side panel, stretching cards) and bigger numbers scored lower; judges reward fewer, calmer elements. Going further needs real content or new features (empty states, onboarding, dark-mode pass), not CSS.
- Landing ingest needs gh on PATH (it fetches the README); a silent ingest failure leaves stale data.json values. The accent comes from the seed page
--accent, not from state/content overrides; dark-green accents under 3:1 on the dark hero need lifting (#2f8a68 gives about 4.3:1).
- A visually hidden node must not add page height: a 1px
.sr-only box at the end of <body> made the page scroll 1px, and Chromium then turns middle-press into autoscroll and never fires auxclick (broke middle-click new tab). Pin hidden helpers with position: fixed.
- Floaters over a dark scene: a cream halo on gold turns muddy. Use light gold with a dark soft shadow, and give the milestone a solid seal chip. Hold opacity to ~60% of the animation; a linear fade reads as “faint”.
- Structural “one focus” changes (reorder, demote a competing card into the summary line) won; colour/size micro-tweaks (r4, r5) tied or lost even when judges had asked for them.
- Append a single “depth layer” block after the base CSS instead of rewriting hundreds of rules; keeps the diff reviewable and responsive overrides in their own media queries.
- Portfolio card text lives in juw12/projects scripts/apps.json; a surgical string replace via the contents API plus one sync-cards.yml run is enough for a text refresh.
- Local probe missed buddy failures on dark cards; always re-probe the deployed URL; use a separate darker ink token for orange text instead of darkening the brand fill (correction from the human)
- Theme bracket via an injected
<style id=theme> over shared tokens is cheap: 4 directions scored in one round; light panels beat dark on a teal 3D scene.
- Test suites click CSS classes (
#askf button.primary, #pf button.primary). Never drop or duplicate primary inside dialogs; restyle with CSS instead.
- Run /design on the app itself, not just the landing page; no emoji anywhere (incl. glyph CTAs); finish all items, leave only what truly needs people (correction from the human)
- Hover background on .btn must not override pressed/primary states: add .btn[aria-pressed=true]:hover or pressed toggles go white-on-white.
- Nongkrong: avoid generic CSS class names like .status on buttons; a layout rule for another .status hid the voice button on phones.
- The onboarding sheet ruins card/landing captures: capture a read-only #visit= link of a stocked tank instead.
- A scroll fade should only render when the panel really scrolls (toggle a class after layout).
- User noticed the 3D did not change between places; earlier pass only themed venue layouts (correction from the human)
- Riak v1.5.0 koi pond: on phones, an absolutely positioned control dock inside a tall canvas stage can hide under a fixed bottom nav. Size the stage as calc(100svh - header - nav), and keep the dock to one icon-only row at 520px or less. Also hide the global pressed-dot ::before on toggles inside icon docks.
- (mint 3.5.4) Latin-script i18n scanners flag every id/es string; use zh as the signal language and treat its leftovers as the real gaps (only brand names should remain).
- (hoki v3.1.1) three.js ShadowMaterial catchers leave a hard slab at the shadow-camera frustum edge on light backgrounds; use a radial contact-blob texture instead.
- New markup classes: grep the whole repo for the class name first (
.pause-label already styled the pause overlay at 1.2rem → topbar button swelled to 41px).
- Blocking force-layout budgets: keep the synchronous pass short (~350 ms) when the view already animates until settled; small graphs still settle instantly.
- Regex-based UI translation leaks captured English (theme names, types, function names). Use function replacements that map the captured words too.
- Flat-colour game skies lerp between keys by time of day; pick a render time on a key (Teduh 07:12), not mid-lerp, or the hero reads grey.
- Landing data: emoji and sparkle glyphs had crept into CTAs/stickers; strip them on every sync (rule: no emoji, no sparkle).
- CSS transitions on roster chip backgrounds get captured mid-fade and fail the A4 contrast audit; transition outline only.
- A5 (app_audit.py) tap-target floor is 32 px, not 24 or 44. Size phone tile buttons to 32x32 or keep them non-interactive.
- Structural variety (seating type, table shape, layout) moved the council; recolouring walls and floors did not.
- A dark theme needs its own scene palette (twilight sky, moon, stars), not a dimming filter on the light scene.
- Landing: in the poster hero a mono display font fits about 13 characters per line and the highlighted span does not wrap; keep the highlight under about 12 characters. The gates and the slop check both passed while the hero text overlapped the emblem; only the screenshot showed it.
- Card:
add_card.py refreshes both clones first and takes 70 to 105 s through the proxy; run it with a log file and poll. It always writes "status": "released" and no click or wait_ms; edit the entry afterwards and re-run portfolio_card.py <id>.
- Vow v13: avoid color-mix() for text colour - Chrome computes it to color(srgb …), which the contrast gate cannot parse; use tokens. Run Playwright gates from the shell tool, not Vector’s Python subprocess (different site-packages, missing browser).
- Card: push the card art before the catalog, then trigger the sync once; the page and image were live within about two minutes. The sync run opens the Substack draft issue and re-opens the “missing cards” issue in the same minute; read both bodies.
- Card:
portfolio_card.py --check audits every card and exits 1 for an older problem on another card; read which card each PROBLEM line names. The sync workflow runs with --allow-card-issues, so a card audit warning does not block the sync.
- Card: for a demo that boots, a
click on a button that stays disabled until the system is ready is a better “ready” signal than a fixed wait; pin a click or a wait so a later re-capture cannot show “Starting the emulator…”.
- Card: a card that shows the product’s key moment says more than an idle prompt; take both and compare. Write alt text that stays true after a re-capture: describe what the screen shows, not a run-specific number.
- Landing: a code line of 77 characters is clipped in the code box at 1,280 px; keep quick-start lines under about 70 characters. Look at the whole page in bands, bottom included, and look again after each fix.
- Driving a native folder dialog without stealing the user’s keyboard: find the #32770 dialog, WM_SETTEXT the path into Edit id 1152, PostMessage BM_CLICK on button id 1. No SendKeys, no IME trouble.
- Bench: on a shared PC, measure two variants interleaved per clip, sample the processor load, and name the condition for what it was instead of calling it calm.
- New landing pages need design DNA (FONTS/STYLE/ART/COPY in design.py + THEMES in enrich.py) before
check passes; publish is blocked until then (good).
- Portfolio “Check project card art” can fail on another card (vow greyscale). Read the failed log before assuming your new card is the problem.
- App: a frozen build of a large app took 14 minutes on a busy PC and its zip several more; start it early and do only light work meanwhile.
- Landing D1 font check also counts custom pages. Add the new font to tools/design.py FONTS before
L check.
- A share link (#r=…) that skips onboarding makes a much better card/landing capture than the home page.
- Landing: the gates do not catch text running past a card at 1280 px (a long URL in the install cards passed
check). Look at a full-page shot at 1280 and 390 before publish; .dev p now has overflow-wrap: anywhere in build_landing.py.
- (mlbb-blame r4-r5) Web Share must run inside the tap. Render the share PNG and text when the result appears, so the Share button only calls
navigator.share; rendering after the tap loses the user gesture.
- (mlbb-blame r3) After a share flow changes from direct download to a preview dialog, the e2e must click the dialog’s Download button inside
expect_download, or the download step hangs.