A product and quality story · 2026

The first tile · this page assembles a mosaic as you read

Mosaicthe Accreditation Companion

You see yourself becoming a coach.

A companion that turns a bureaucratic coaching credential into something you can watch yourself grow into, one session at a time.

Home screen: a growing ceramic mosaic labeled Who You’re Becoming on indigo silk, beside today’s intention and the card of the day on cream. 1 2 3
  1. 1 The mosaic, grown from your name and your practice.
  2. 2 The File % lives in its own ring, apart from the mosaic.
  3. 3 The daily ritual: an intention and one reflective card.

The short way in

What I did here, in sixty seconds.

I am a QA engineer. I wanted to find out what quality work looks like when one person owns the whole thing, so I took a product from an empty file to a live build and kept it honest through 247 versions.

I set the problem, designed the screens, specified every behaviour, reviewed what the AI wrote, and then tested the result as though somebody else had built it. That last part is the work I care about, and chapters 09 and 10 put the evidence on the page instead of summarising it.

  • 247versions, every one a complete build
  • 18risks scored, each mapped to a suite
  • 100%mutation kill-rate on the suite itself
  • 5gaps found after self-review said done

01 The wall nobody warns you about

People don’t get stuck at the coaching.
They get stuck at the paperwork.

Accreditation is a real credential, earned in an EMCC-aligned program: twelve documents in a fixed order, a quota of practice hours, an essay, an exam, one deadline.

The coaching is the part people are good at. The File is where the hours scatter across notebooks, the reflections get postponed, and the dread sets in.

  • Fear one · the clock

    “Will I gather it all in time?”

    A date that doesn’t move.

  • Fear two · the mirror

    “Am I becoming the coach I need to be?”

    The one no spreadsheet has ever answered.

energy the low point energy thinnest, work heaviest Classes First clients Hours pile up The File panic File submitted

My own year in the program, drawn honestly. A tool for this road earns its place at the low point, or nowhere.

The welcome tour on a first launch: a warm six-step overlay above the Home screen, which previews a grown coach’s demo state.

Where everyone starts: a welcome tour. One trick, named TOUR_DEMO in the code: the tour borrows a grown coach’s state, so the road ahead is visible before yours begins.

So what already exists for this?

02 A gap shaped like one person

First I went looking for the tool that surely already existed for this. It doesn’t.

generalist accreditation-native business ops personal becoming Enterprise coachingBetterUp · CoachHub Practice managementCoachAccountable · Simply.Coach Mass wellnessHeadspace · Calm LMS / courses Journaling apps quiet for a reason: there’s no business to run before the credential exists Mosaic the empty corner

The competitor was never one of these. It was a free spreadsheet and a group chat, and nothing anywhere was built around an accreditation.

One person, the whole time

A coach-in-training partway through a serious program, phone-first, allergic to cold corporate tools and to gamified confetti alike.

The evening I designed for: the session ends at 21:40, the note is “for later,” and the File is 76 days away.

Reduce the two fears, and you create value. Amplify them with density or pressure, and you work against the product.

What “accreditation-native” means

PRACTICE Client Loghours · clients Supervisiongroup hours Mentoring1:1 hours Peer grouppeer hours REFLECTION & ATTENDANCE Reflectionnotes · a set mix Attendanceminimum days Signed clientfeedback forms MODEL & ASSESSMENT Coachingmodel essay Live 30-minevaluation IDs, CV,exam

Twelve documents, eight competency areas, thirty indicators, exact thresholds: encoded, not translated. That domain knowledge is the moat.

Progress screen: the 12 File documents as a vertical road with per-document progress, a 38% summary ring, and a visible 1 October deadline. 1 2 3
  1. 1 One honest number: the File %, computed from the entries themselves.
  2. 2 The deadline sits in the corner, in view and out of the way.
  3. 3 The 12 documents as a road of stations: start anywhere; everything grows in parallel.
Detail, actual size: the summary band reading You’re at 38% of the file, with the File deadline chip on the right.

The same band, at reading size: the whole road in one line. Nothing blinks, nothing scolds.

The corner is empty. What belongs in it?

03 The bet

What if progress didn’t feel like a checklist, but like a portrait of who you’re becoming?

The centerpiece is a mosaic of broken ceramic, a trencadís, grown from practice, one shard at a time.

The name sets the pattern. Each session adds a colored piece. You don’t fill a bar; you assemble a self.

The one rule that governs everything after: mosaic ≠ File. The soul and the accounting never share a surface.

Filmed from the shipped build, seeded one step at a time: first a session, then a note, then an hour of supervision. One step, one shard; the counter is the app’s own.

Empty mosaic: the invitation state, no shards drawn at all.

Nothing yet, only the invitation to begin.

A grown mosaic on mobile Home, thirty-one shards around a golden heart.

Thirty-one shards later: the same person, visible.

It grows without a ceiling. There is no “complete”; only more of you.

A metaphor is cheap. What makes this one structural?

04 The shapes I broke

A good idea on a slide. A terrible design in practice, at first.

Trencadís means the act of breaking. Fitting, because the metaphor only survived by breaking the wrong versions of it.

  • ✕ Rejected as centerpiece

    The percentage

    The ring survives for the File math, but as the centerpiece it fed the very clock-fear the product tries to calm.

  • ✕ Rejected on sight

    Auto-filled shards

    A starter mosaic drawn from nothing: a self you didn’t earn. One render was enough.

  • ✕ Shipped, then unshipped

    The streak

    Shipped in v153, pulled by v185. A chain you can break is pressure; what stayed is a plain count of days with the ritual.

    The chip itself, from v153: a small pill reading 5 days in a row.

    The chip, from v153: twelve days from ship to unship.

  • ✓ Kept

    Name-born, practice-grown

    Causal, or nothing: one piece of practice equals one shard, and the name seeds the pattern. The mosaic can only say something true.

  • I thought

    a percentage would motivate.

  • But it

    whispered “you’re behind”, the exact feeling I was trying to dissolve.

  • So I

    split soul from accounting. Numbers stay honest; identity stays warm.

Close-up of the shipped mosaic: irregular ceramic shards in violet, gold, cream and deep teal, edged in gold, around a golden heart.

What survived, up close: the shipped Home. Each shard stands for something that actually happened; the gold heart is the name.

The metaphor held. Could a whole interface speak it?

05 Silk & filigree

One system, two temperatures: indigo silk for identity, cream parchment for the work. Built to escape the generic coaching-app look.

Type carries it

You build yourself from shards.

Cormorant Garamond: display, italic, the voice.

Hi, Alex — almost there — 1 attendance day left to tick.

Jost: UI and data. The greeting above is lifted verbatim from the Home header; both faces are self-hosted.

Color is an API, not decoration

Emerald · done Gold · active Garnet · destructive only Orchid · yours Lila · practice Azulejo · mosaic ceramic,never a status

Garnet only ever means “this deletes.” One color, one job.

The emotion wheel at readable size: an inner ring of seven families, Happy, Surprised, Bad, Fearful, Angry, Disgusted and Sad, opening outward through two further rings into about eighty precise words, with the question How do you feel? at the centre.

The palette at full stretch

Seven families in the middle, three rings outward, about eighty words, one question at the centre. Every ring is the same six hues doing the same jobs they do everywhere else in the product.

This is the piece that convinced me the colour system was load-bearing. Eighty labels have to stay legible, stay distinguishable from their neighbours, and never accidentally say “warning.”

It opens inside the Atelier, one tool among the thirty-nine on the shelf above.

Atelier: 39 coaching tools grouped by process phase, with search, favorites and a read counter.

The system at work: the Atelier of 39 tools from the program’s manuals, grouped by process phase.

Detail: two tool cards crediting their sources: after Alain Cardon, and Otto Scharmer.

Each tool is credited exactly as the manuals cite it: Cardon, Scharmer, Rosenberg, Whitmore.

The Atelier on mobile: search, category filter, favorites, and the same credited tool cards.

The Atelier, pocket-size: same tools, same credits.

A beautiful shelf is still a shelf. Where does the work happen?

06 One fact, everywhere

Write a session once. It becomes five things.

Live session cockpit: a timer, the client, a searchable tool list, and a notebook of lines classified as belief, emotion, value, question, tool and action. 1 2 3 4
  1. 1 A resilient timer: the session survives even a reload.
  2. 2 Write a line; one tap classifies it: belief, emotion, value, question.
  3. 3 A tool you use drops a golden line, right in the thread.
  4. 4 The classify row: one tap, no menus, so the client never waits.
Detail, actual size: seven notebook lines: a plain note, a belief in quotes, an emotion, a value, a question, a golden tool line, and an action.

A line per thought, each with its colored dot, written while the client is speaking, without leaving the conversation.

Where one session goes

▶ Live sessionthe cockpit above Saved to the logalready structured The File Export ↧ 1 · hourstoward the quota 2 · tools usedfeed Atelier stats 3 · discoveriesthe session record 4 · actionsnext steps, kept 5 · reflectionwhat I’d improve

swipe the flow →

End the session and it lands in the log. Nothing gets typed twice; the cockpit is a door into the File.

The exported File document: the official numbered order: personal documents, CV, the course attendance list as a table, and the Client Log with initials, hours and topics.

And what comes out of that door: the exported File itself. Each row filled from what was written once; initials only; totals derived.

Mobile Progress: the File card with a completion ring, counters, and the visible deadline.

Dated stops: the calendar and deadline, shown plainly.

Mobile attendance stop: 8 of 12 days, module dots, and an Open list button, ticked by hand.

Personal stops: ticked by hand, never by the calendar.

Clients master-detail: a list of clients by initials, and a selected client’s session thread with dates, goals and tool chips.

Clients, one ledger

Initials only: data minimization as the default. Each session carries its date, goal and the tools actually used. The client record is the single source of truth for the feedback requirement.

And phone-first on purpose: sessions happen in rooms and on calls, rarely at a desk.

07 The rules I wouldn’t break

A product about honesty has to stay honest when it’s inconvenient.

Desktop Home with no name and no steps: the mosaic area shows only an invitation; nothing is drawn.

The strictest state in the product, on the biggest screen.

Zero means zero

With no name and no steps taken, the mosaic does not draw, anywhere. No starter shards, no simulated identity; only the invitation.

The rule lives in the render path: an empty name yields zero shards, not a decorative minimum.

Honest by construction

A date passing doesn’t mean the work happened. So dated events auto-mark; personal work never does; you tick it yourself.

I refused to auto-create the reflection note. The official notes have a required shape; faking one would poison the single thing this product sells.
A date passed Fixed event? → auto-mark ✓ module, deadline Personal work? → never you tick it yourself

A room of its own for reflection

The official reflection notes are the heart of the File, and the one thing I refused to automate. So writing one isn’t a form; it’s a full screen: Facts, Feelings, Findings, Future, prompts folded away until you ask, a draft that keeps itself.

The header says what the manual asks: ~700–800 words, first person. In the corner: “saves by itself.”

The reflection room on mobile: an indigo header with the note's title, then the four F chapters with prompts folded and space to write.

The same room, in a pocket.

The reflection room on desktop: date, event and topic fields, then Facts, Feelings, Findings and Future chapters strung on a single golden thread.

The four F chapters on one golden thread. The room writes nothing for you; it holds the shape the credential requires.

The official composition tracker: each required note type (client sessions, supervision or feedback, book review) with its own progress.

The stop panel knows the credential’s required mix and still leaves every word to you.

Integrity, in motion

Late in the process, an adversarial review caught the app labeling the user with a gendered word, an easy slip in Romanian grammar. The fix shipped the same day.

Beforeprezentă: “present,” in a form that assumes the user’s gender
Afterai participat · you attended: the fact, for anyone

The product is for anyone. A voice check now guards this on each release.

Detail: Module I with dates 29–31 January and three day toggles, each reading you attended.

The label, today.

08 What 247 versions taught me

247

times I was willing to change the product

Seven weeks, one HTML file, and every version a complete build that could have shipped. Each one began as a decision about what the product should become, went out as a specification, came back as code I reviewed against real pixels, and then had to survive being tested before it counted. The AI wrote the code. Everything upstream and downstream of that was the work.

The number on its own proves nothing, so I went back and pulled four of those builds out of git and ran them again, on the same seeded profile, this afternoon. What follows is what actually rendered.

v145 · the dashboard era

Home in v145: a six-petal progress flower over indigo, with a legend of six pillars and their percentages; the greeting is a single line above it.

Six petals and six percentages. Identity was a subtitle sitting above a number.

v153 · the mosaic arrives

Home in v153: the mosaic takes an indigo identity card on the left; on the right the dossier ring and the card of the day, which carries a small chip reading 3 days in a row.

The mosaic takes the left column. The streak chip from chapter 04 arrives with it, at the top right of the card of the day.

v185 · the streak is pulled

Home in v185: the same identity column, and on the right today's step, today's intention and the card of the day; the streak chip is gone.

Same seeded profile, three days later. The chip is gone. The mechanic worked, and working was the problem.

v247 · the current build

The version at the top of this page: annotated, one review gate from production. v242 is live today.

These three frames were rendered for this page from the builds themselves, pulled out of git and run again on one identical seeded profile, so the differences between them are the product changing and nothing else.

The road: every version, one tile

  • v1 a plain tracker
  • v48 Silk & Filigree
  • v63 EMCC alignment
  • v88 the daily ritual
  • v152 desktop-native
  • v201 the home diptych
  • v217 zero means zero
  • v226 security + adversarial QA
  • v235 “you attended”
  • v242 one date standard
  • v247 the current build

Each tile is one of those decisions, which is why this is a wall of tiles rather than a gallery of screenshots. Four are dug out on this page. The rest are the ordinary work of changing one thing and checking that everything else still held. Versioned by name, never overwritten, each one through the same automated gate before it deployed, and the later ones through something harsher.

The rule from chapter 07, before it was true

What a promise looks like when the product is not keeping it

Chapter 07 says the mosaic does not draw until something has been earned. That rule exists because for a while the opposite was shipped. Below is one empty profile, no name and nothing practised, opened in two builds. Both frames were rendered for this page from the builds themselves.

v153 opened on an empty profile: the greeting reads Hello with no name, a small mosaic of three shards is drawn anyway, and the line underneath reads from your name, 3 shards.

v153. No name, nothing practised, three shards drawn regardless. The label credits a name that was never written.

v217 opened on the identical empty profile: nothing is drawn. A dotted circle invites you to write your name in Profile so the mosaic can take shape, and the counter underneath reads zero shards in your mosaic.

v217. The identical profile. Nothing is drawn, the counter reads zero, and the space asks for a name instead of filling itself in.

This is the one I would put in front of a hiring manager first. It is a product decision, a copy decision and a test case at once, and it is why the empty state now has an assertion of its own in the suite.

  • On honesty

    It’s a design system: most decisions were about not showing things.

  • On metaphor

    It only works if it’s structural. Otherwise it’s wallpaper.

  • On voice

    Cheaper than UI, and it changes more. Reframing language moved the whole feel.

Told honestly

A web preview, not a native app. Data lives on the device; the seam for a real backend already exists in the code. GDPR at scale is named openly as a gate. The limits are on the page, not under it.

And where it stands, told the same way: live, in daily use by the person it was built for. My clients and my hours live in it; my File walks toward an October deadline. What it still needs is proof from beyond me, other people’s Files. That pilot is the road ahead.

Two hundred and forty-seven times I decided a build was ready. On what evidence?

09 How I tested

The gate that runs on every ship is only the floor. Above it sits a program I wrote as though the product belonged to somebody else, because a suite written from the code can only ever agree with the code.

A product makes promises. This one promised that nothing appears unless it was earned. Most of what I found came from taking that sentence literally.

i

The risk model came before the tests

I did not start from the screens. I started from what could hurt the person using this, scored each risk by impact times probability, and let the score decide what earned a suite and what earned a gate. Eighteen risks in the register. Eight of them here, with the reason each one exists.

  • R-01P0score 20

    Data loss or corruption

    There is no backend. Months of a trainee’s work sit in one browser on one device. Every other risk is measured against this one.

    qa_persistenta · qa_fuzz_boot
  • R-02P0score 20

    Boot brick

    One stored value the code never expected, and the app opens to a blank screen. This happened for real at v233, which is why it scores where it does.

    qa_fuzz_boot
  • R-03P0score 15

    A File that does not add up

    The exported File is the deliverable. If the hours and the twelve documents disagree, the user hears about it from the school, months later.

    qa_exporturi · qa_e2e
  • R-04P0score 15

    Stored injection

    Everything the user types is rendered again somewhere else. One unescaped field is enough.

    qa_securitate
  • R-16P0score 15

    The platform deletes the storage

    iOS can evict an unused installed app’s storage after about a week. Nothing in the app’s own code defends against the operating system.

    qa_pwa
  • R-09P1score 12

    Time slips

    Midnight, time zones and dates that cannot exist. Cheap to get wrong, and invisible until a deadline moves by a day.

    qa_timp
  • R-11P1score 9

    Visual regression

    Something cut or overlapping on a screen nobody touched. A correct document tree proves nothing here.

    qa_sweep
  • R-18P1score 8

    The suite lies

    Weak assertions pass forever and feel like safety. This is the risk that the safety net is itself the problem.

    qa_mutation

Fifteen and above is P0, and a red P0 does not ship. Nine to fourteen is P1 and needs a written decision recorded in the changelog. The register and the risk-to-coverage matrix are generated by the run itself, so a risk cannot quietly lose its coverage.

ii

The gates, and what runs them

Four gates, sized by how much time each is worth. A quick one on every staging push. A mandatory one on every ship. A release gate that adds performance, the visual sweep and mutation. And one that runs against the live URL after the deploy, because local is not production.

The deploy gate

The whole file boots in a headless browser, a seeded profile walks every screen, a client is created through the real form, and the app resets through its own dialog. Fourteen assertions. One red and the deploy script refuses to publish. The output is translated from the Romanian it runs in. The v122 in its header is the test script’s own version rather than the app’s.

$ npm run smoke
--- TESTS v122 ---
  CSS parsed (>200 rules)
  :root present
  not onboarding (storage seeded)
  Home: greeting with the name “Ines”
  Home mosaic drawn
  nav, 5 zones
  language EN (“Home”)
  navigate Progress
  navigate Clients
  navigate Atelier
  navigate Profile
  client added T.E.
  reset cleared clients
  zero runtime errors
Result: 14 passed, 0 failed

Above the floor

A risk-based program of 199 automated cases across twelve suites, plus 200 fuzzed boots on every ship run and ten visual goldens. The suite is itself tested. The current mutation run kills every mutant it is given.

The rest of the machinery
  • Visual goldens

    Screens rendered at real viewports with the clock frozen, compared pixel by pixel against baselines committed to the repo. A screen I did not touch coming back changed is the signal. Baselines are re-based on an explicit decision, never to make a run go green.

  • Preflight router

    Reads the diff and decides which gates have to run, so a copy change does not pay for a full visual sweep and a visual change cannot skip one.

  • Voice guard

    An automated check for an editorial rule rather than a functional one. The product must never assume the reader’s gender and carries no coloured emoji. Both are easy to state and easy to lose, so a script owns them.

Ten mutants, so the suite has to earn its green

Passing tests prove nothing on their own. These are real changes injected into the running code, one at a time, to see whether anything notices. A mutant that survives means an assertion was decoration. Four of the ten:

  • The practice hours add up backwardsh += Number(s.dur)h -= Number(s.dur)
  • The spreadsheet injection guard stops guardingreturn /^[=\t\r]/.test(s) ? "'" + s : sreturn s
  • Unticked attendance days count as attendedObject.values(presence).filter(Boolean).lengthObject.values(presence).length
  • The reflection quota counts as met far too earlyMath.min(1, met / 5)Math.min(1, met / 1)

Every one of the ten was caught. That is what the hundred per cent refers to, and it is the only number on this page that says anything about the tests themselves.

iii

Independence, and how I prove it

A suite is only independent if something forces it to be. Three things do that here. The one worth showing is the last.

Above the gate sits an adversarial review, an independent pass with one mandate: prove the fix wrong, and back every finding with something runnable. On the last hardening batch, self-review had already declared the work done. The adversarial pass found five gaps. All five shipped fixed. The worst of them:

Attackpoison the saved state: point the daily card’s index past the end of the deck, then reload
Defenseboot now validates every stored value against what consumes it; an impossible index falls back to a safe one instead of taking the app down

No ordinary use produces that state. That is the point of the exercise.

The other two, and why they matter

Written from the requirements

Every assertion derives from the program’s written requirements rather than from the implementation: the hours, the documents, the attendance and the quotas, each exactly as the rules state them. Numbers read off the running code live in a separate characterisation net, labelled as the developer’s safety net and never counted as evidence.

Proved differentially

A probe that only passes on the current build proves nothing about the bug it claims to cover. The key probes also run against v228, the build from before the fixes. v228 fails on sessions:"abc" and v234 does not. That difference is what turns a passing test into evidence.

What I do not check is written down too. Headless is not a device, local is not production, and the risks with no automated case stay marked as manual in the matrix instead of quietly counting as covered.

The program found things. What does a finding actually look like?

10 What I found

Findings from the independent program, in the format they were written in at the time. Severity, the risk they belong to, how to reproduce them, what was expected, the proof, and the fix. All of them are closed.

v233 booting with the poisoned save: a completely blank cream screen; nothing renders.

v233, the last vulnerable build: the attack lands. Nothing renders.

v234 booting with the exact same poisoned save: the Home screen renders normally, mosaic and all.

v234, one version later: the same save, gated. Home simply renders.

The adversarial finding, reproduced for this page: one poisoned save, two builds recovered from git, identical seeds.

Three defects, three different kinds of thinking

The first is about data integrity and was found by a test. The second is about the platform underneath the product and could not be found by clicking at all. The third is about interaction state and was found by a tool. Each one needed a different instinct.

S2 · major QA-BUG-001 · risk R-04 · found at v234

Stored injection through the date fields

What
Values held in state.dateOverrides and state.targetDates reach the screen unescaped. They pass through the date formatters into the Progress tab and into the exported File, and are inserted as raw markup. An <img src=x onerror=…> becomes a live element in the document and runs in a real browser.
Vector
A hostile imported backup. The import warns that it replaces existing data, but it does not validate these two objects key by key. It only checks that each one is an object. The normal path through the interface is safe, because there the value comes from a date input.
Reproduce
Import a backup containing {"dateOverrides":{"dosar":""><img src=x onerror=window.__pwn=1>"}}, then open Progress and export the File.
Expected
Date values escaped on render, the way every other user-supplied field already is. They were not.
Proof
tests/qa_securitate.mjs, case S-14, reporting true on both render sites.
Severity
S2 rather than S1. It needs a backup the user imports deliberately, so there is no anonymous third party in the chain. It is still a real escaping hole, in the same family as the ones already closed.

Fixed in v236

Two fixes were available. Escaping at render would have closed the two sites the test found. Validating at the boundary instead makes migrate() discard anything in those objects that is not a YYYY-MM-DD date, which makes the sentence “these objects only ever hold dates” true of the whole program.

I took the second one. It covers every render site, including the ones nobody had looked at, and the legitimate path is untouched because a date input can only produce that shape. Case S-14 stopped reproducing on both sites.

S2 · major QA-BUG-002 · risk R-16 · found at v234

The app never asks to keep its own storage

What
The app never calls navigator.storage.persist(). Without it, iOS Safari can evict the local storage of an installed app that has not been opened for about a week. With no backend behind it, that is total data loss for a trainee with an iPhone and a quiet seven days.
Why it ranks
The largest real vector for losing data is not a defect inside migrate(), which is by now well defended. It is the operating system. Recovery code protects against corrupted data, and nothing in the app protects against deletion by the platform.
Reproduce
Not reproducible headless, because the behaviour belongs to the OS. Confirmed instead by the absence of the call in the source, where the word persist appeared only inside comments.
Proof
tests/qa_pwa.spec.mjs, case W-10.

Fixed in v236

The app now requests persistent storage once at boot, after the first render, guarded and non-blocking. The browser grants it based on engagement. It is a cheap mitigation and not a guarantee, so a periodic backup prompt stays on the backlog and the real answer arrives with the backend.

The case itself was also wrong, and that is worth saying. It read the live document and, when it landed a few milliseconds after a service-worker reload, it intermittently reported fixed code as vulnerable. It now checks what the server delivers. A test that lies costs more than the bug it was watching.

S3 · degraded QA-BUG-003 · risk R-12 · found at v234

Interactive controls nested inside an interactive card

What
On the Atelier tab, axe-core reports nested-interactive on around forty nodes at 390px. A favourite star and a chevron sit inside a card that is itself a button. Screen readers and keyboard navigation cannot resolve which control has focus.
Reproduce
Run the visual and accessibility sweep, then open the Atelier tabs at 390 and at 1440.
Proof
tests/qa_sweep.spec.mjs, the axe-core pack.
Severity
S3. Accessibility is degraded on one screen and nothing is functionally blocked. The rest of the app passes axe critical and serious cleanly across five tabs and two viewports.

Fixed in v236

The card was restructured so the expand button holds only the title, and the star, the pencil and the chevron became its siblings inside a wrapping row. The toggle moved to that row, so a click on the title or the chevron still opens the accordion and Enter on the button still works.

The part I care about is the proof that the fix cost nothing. The visual sweep came back with a pixel difference of zero, so the layout did not move, and the favourite toggle still fires without closing the accordion. Axe critical and serious went to zero across all five tabs and both viewports.

What I would bring to a team

Four habits, and every one of them is somewhere above with its evidence attached.

  • I read the product first

    The register in chapter 09 is not a generic checklist. Every line in it comes from knowing what this product is for and who loses what when it breaks.

  • I fix the invariant

    Closing the two render sites would have turned the test green. Making the illegal value impossible to store closed the sites nobody had looked at.

  • I test my own tests

    Mutation runs, probes proved differentially against an older build, and a flaky case I found in my own suite and made deterministic.

  • I say what I did not check

    Headless is not a device and local is not production. The limits sit next to the results, where somebody can argue with them.

11 The whole, from broken pieces

Profile: the name that seeds the mosaic, the story of the shards, and the on-device data promise.

The story, told back

The Profile is where the product says it plainly: this pattern came from your name; these shards came from your practice; the data stays on your device.

You don’t finish with a File.
You finish able to see who you’ve become.

Mosaic, the Accreditation Companion: becoming, made visible.