Agreement
Monthly repayment
£214.60
View schedule
ux-factory is the tool I am building to take a product idea from its first question to an engineer-ready handoff. Every decision is kept with its evidence, and every screen draws on one token contract. The agents' runs are committed beside the work. This page shows how it works, what runs today, and one Lendable-shaped idea sketched for it.
A static portfolio shows finished screens. It cannot show how the decisions behind them were made, so the reader takes the process on trust. ux-factory runs the process and commits the evidence: the questions asked, the answers given, the agent runs and the checks that passed. The second problem sits earlier. Design usually starts after someone has already decided what to build, and that discovery ends as prose nobody can audit. Here the decisions are recorded first, and the build reads them. So the project has two jobs. Today it is a portfolio you can verify. Its finished state is the tool for making the next real product.
Each station takes what the one before produced. For a product team at a lender the order matters. What to build, and for whom, is answered and written down before any screen exists, and every screen can point back to the decision that asked for it.
Epic #295, open.
A lending product carries calls about affordability, vulnerable customers and what happens when a payment fails. When those calls live only in a meeting, nobody can check them later. Decide asks them as questions, pushes back on thin answers and keeps the reason and the kill criterion beside each decision, so a designer can show why a screen exists.
76 entries over nine stages: 65 from the source research, plus 11 added (four non-functional, six on AI interaction, one on scope parked for later).
Each entry names its source method, carries a provenance label (observed, derived or thin) and a weak-answer note: what a weak answer to it sounds like.
A session asks a selection, never the whole bank: scope check 6, opening set 12, full discovery up to 31.
Runs in the local portal. The agent judges the form of each answer against that question's weak-answer note, never its substance. It may not say an answer is wrong.
When an answer is weak it names what is missing and asks once more. The second answer closes the question. There is no third ask.
record_decisionflag_weak_answeropen_questionfile_evidenceA decision op has no field for answer text. It points at the person's answer by reference, and only the server writes answers, verbatim.
Projected from the recorded ops by a pure fold: the same run always gives the same page, and a claim the ops do not carry cannot appear on it.
wrong_if. The applier refuses a decision without one.In progress Follow-on epic #504: grading 11 committed PRDs on 22 criteria found no decision log, acceptance criteria or sign-off line in any of them. Those sections are being added.
Each decision is filed at one level and names the decision above it that it serves. A decision with no parent is flagged orphan. The levels come from BABOK.
A screen composed on the canvas cites the decisions it answers, by their number in the ledger.
Still open: building one real flow from its PRD on the canvas (#316), and links from each decision forward to its screens (#514).
Illustrative. Built from Lendable’s public site only; not Lendable work. The question and its weak-answer note are real bank entries. The answers, follow-up and decision are written for this page, not taken from a recorded run. Rate path is the idea in section 07: after a soft-search quote lands in the Fair or Limited band, show the total cost in pounds beside a better band, the factors that move a band, and an opt-in soft re-quote later, with “borrow now” kept as an equal choice.
“What would this metric look like if we were gaming it, and what counter-metric would catch that?”
“We track how many people opt in to the later re-quote. If that goes up, Rate path is working.”
“The answer names one metric and no counter-metric. Opt-ins would also rise if the screen talked people out of borrowing now. What would you see if that were happening, and which number would catch it?”
“Gaming it would look like opt-ins going up while people who opt in never come back for the re-quote. That would mean we delayed them rather than helped them. So the counter-metrics are two. Among Fair and Limited quotes, the share choosing borrow now should not drop against the screen without Rate path. And people who opt in should reach a re-quote and see their band hold or improve.”
The job description asks for a consistent visual language and for inconsistencies to be flagged. That gets harder as more screens come from more hands, some of them AI tools. Here every component reads the same semantic names, and the values behind them come from one source file, so web, iOS, Android and the Figma import path start from the same place.
Layer 1
tokens.contract.css
Every semantic token a component may use, each with a neutral fallback. It never carries a brand, so a page still renders with no pack loaded.
Shipped
Layer 2
tokens.neutral.css · tokens.verdant.css · tokens.saulera.css
Primitives (the brand's own values) bound to the semantic names. Re-skinning a whole site is one <link> line in each page's head.
Shipped
Layer 3
components.css
No literals: every colour, space and radius is a semantic token. Token lint in CI fails any token a component uses that the contract does not declare.
Shipped
The card markup and its CSS are identical in both. The only difference is which values the wrapper binds to the semantic names. This is the mechanism a brand pack uses, at the scale of one element.
This page's pack
Agreement
Monthly repayment
£214.60
View schedule
Inverse set, six overrides
Agreement
Monthly repayment
£214.60
View schedule
.scope--inverse {
--color-bg-surface: var(--color-bg-inverse);
--color-fg: var(--color-fg-on-inverse);
--color-fg-muted: var(--color-fg-on-inverse-muted);
--color-border: var(--color-inverse-line);
/* …two more, all token to token */
}
Illustrative card and figures, built for this page.
Source of truth
system/tokens.source.json
W3C Design Tokens format. A new token enters the contract group here first, with a neutral fallback; brand values bind in the pack.
Shipped
Generators
agent-layer/*.mjs, with Style Dictionary for the platform builds
Drift-check gate
CI regenerates every output in memory and compares it with what is committed. Any difference fails the build, so nothing downstream is edited by hand.
Shipped
The handoff pack · never edited by hand
The pack is generated and committed today. Six slices that close its remaining gaps are open, so the pack as a whole is Partly shipped
tokens/css/contract.css · neutral.css
Shipped
FactoryTokens.swift · tokens.xml
Generated today. Spec-matching token names, the contract group and type tokens for both platforms are an open slice.
In progress
pack.json, from system/specs/*.md
Shipped
contracts/*.contract.json · JSON Schema
Shipped
vocabulary.json
The only parts an agent may compose with.
Shipped
wc/vd-*.mjs
Shadow DOM, styled only by the tokens the spec declares. Three data-bound components so far.
Shipped
llms.txt
One line per pack file: what it is, and when to read it. Generated and gated.
Shipped
components.css · bindings · one command contract
So an engineer can wire a real service to the pack without asking design. Open slices.
In progress
figma-import.md · tokens.dtcg.json
Four import routes documented. The parity run against a real Figma file has not landed yet.
In progress
The job description expects AI in daily work. In a lending product that raises a plain question: who decided this screen, and on what grounds? In ux-factory a person writes the brief and approves anything new. The agent proposes from a fixed vocabulary, and each run is committed so a reviewer can replay it and check.
127.0.0.1 portal, never deployed
The problem, never the answer. No board, no worked example.
ShippedOne of the eight verbs in the op vocabulary, nothing else.
ShippedThe run is fenced: no file writes, the brief is the only read.
ShippedA refusal is shown by name, never a silent fallback.
ShippedA proposal joins the vocabulary only on the owner's word; every gate runs and the diff comes back.
ShippedNamed rules checked against each committed composition. It reports raw counts and gives no score.
ShippedWhat the agent did, refused and corrected stays readable.
ShippedRaw and curated pair in the repo, beside the board it built.
ShippedPublic site, the reader's browser
No framework, no build step, no runtime dependency.
ShippedThe committed ops play back at the run's real pacing.
ShippedThe label lives in the trace file itself, and the raw run sits beside it.
Shippedaskproposeadjust
ShippedEach replayed card shows whether the agent said it or did it.
In progressOpen the trace and check the run yourself.
No live model at view time: no per-visit cost, no prompt for a reader to steer.
A bad run is fixed by a tighter prompt and a re-run, never hand-edited.
Edge and error states are where a lending product can hurt someone: a missed payment, a failed affordability check, a payee name that does not match. A check that fails the build when a declared state is missing keeps those screens from being dropped late. Each check is code in the repo that a reviewer can open and run.
A branch, often written by an agent.
Journey drivers, morph checks, the composition judge. Run by the operator; they do not block a merge.
Opened ready for review.
Four jobs: verify, visual, codeql, audit.
gates-green
The one check branch protection on main requires.
Into main.
A manual operator step; nothing deploys on merge.
token-lintFor a designer: a component cannot ask for a colour, space or type step the brand contract does not define, and dead tokens do not pile up.
verify job stops and names the undeclared or orphaned token.drift-checkFor a designer: the token files, icons and handoff pack a developer receives are exactly what the source produces. A hand-edited generated file fails.
build-checksFor a designer: every component in the vocabulary has a render path, and a broken composition rule fails by name instead of shipping an empty box.
visual-regressionFor a designer: an unintended visual change to a shipped page fails the merge. An intended one must re-baseline in the same pull request, so it shows in review.
visual job goes red and uploads a diff report showing what moved.feature/v3-* branches it reports but does not block, by design.For a designer: the edge and error states in a flow are exercised in three browser engines, not only the happy path.
vt-verifyFor a designer: motion that silently stopped firing is caught, and reduced-motion users still reach the end state.
vt-stack-auditFor a designer: adding motion cannot quietly break the layout people see when nothing is moving.
For a designer: copy rules are checked on what the agent actually produced, and the judge is never fed back into the prompt.
For a reader: an agent run shown on the site is the recorded run, not a re-authored one.
drift-check names the trace or replay file that disagrees.For a reader: the documentation cannot claim more coverage than the code runs.
drift-check names the document whose count disagrees.codeqlmain. Any open high or critical alert fails. The 14 high alerts it first found were fixed in code, none dismissed.main carries them.build-checks and review.auditgates-greenverify, visual, codeql and audit all succeeded, reading the pixel gate’s true outcome. A pull request cannot merge until it is green.
For a designer: what reaches main has passed every CI gate above.
main moves after it ran.Not gated: the screen-reader pass and judgement-level accessibility (labels that say what they do, colour never the only signal) are checked by hand. No automated accessibility scanner is in the stack.
Planned, not yet a gate: a conformance check that tests a running service against the handoff pack’s contracts (contract-check, in progress).
main, and each gate states what it cannot see. Source: the repo’s gate reference and CI workflow, as of 3 October 2026.An illustration, built from Lendable's public site only. It is not Lendable work. The idea sits where a product designer at a lender spends real time: a person who has just seen a quote, a choice between borrowing now and waiting, and copy that has to keep both open without nudging either way.
Shown at full capacity, the finished factory. What runs today is in section 08.
Illustrative: an idea sketched for the factory, built from Lendable’s public site. Not Lendable work, no inside knowledge.
Hypothesis to test People who check their rate and land in the Fair or Limited band either borrow at the highest cost or leave, and neither outcome is good for them or for the lender.
The idea: after a soft-search quote in a higher band, show the total cost in plain pounds next to a better band, the two or three factors that most often move a band, and an opt-in to be quoted again later with another soft search. The person can choose to wait without losing the journey, and borrowing now stays an equal choice.
From the public site only: personal loans of £1,000 to £25,000 over one to five years; “See your rate before you apply - it won’t affect your credit score”; offers set by credit band (Great, Good, Fair, Limited); “committed to responsible lending”.
Discovery drawer · run package
Artefact
A run package
The problem recorded as a problem, never as the answer. A real company’s package stays in a private folder and is never committed to the public repo.
Question bank · op applier · PRD projection
Sample exchange
Question “What happens today, and how do you know?”
Weak answer “People in the lower bands drop off.”
Agent “Drop off where: at the quote, the offer or the drawdown? Which number shows it, by band?”
Artefact
prd.md
Generated from the recorded answers, never written by hand. The decision line:
Decision: offer a wait-and-re-quote path beside the quote, with borrowing now kept equal.
Evidence asked for: quote to offer to drawdown, by band. Marked unknown until supplied; the factory asks for it and never invents it.
Kill criterion: stop if opted-in re-quote uptake, or later approval in a better band, does not beat a stated baseline, or if complaints rise.
Per-company brief · derivation engine · ethics gate
Ethics gate The same engine runs the Hooked frequency filter. A loan is taken rarely, so the verdict is utility: get in, do the job, leave, with habit mechanics rejected. Two answers place the idea on the Manipulation Matrix, and the target is facilitator: it improves the person’s life and the maker would use it. Nothing in the flow may nudge towards borrowing.
Artefact
A private, derived pack
It lives only in the private folder and an unlisted instance. This page carries none of Lendable’s logo, fonts or colours.
Free canvas · component vocabulary · ledger · ratify
Artefact
Screens, each a base plus overrides
Error and edge states
Each state stores only what differs from its base, so a fix to the base reaches every state. The screens are drawn in the product, below.
A built example of the same thinking (fictional lender): missed payment · early settlement
Build checks · journey drivers · pixel gate
Artefact
A gate record: what fires on what
Handoff pack generator
Artefact
The handoff pack
llms.txt indexInstance builder · replay · trace player
Artefact
A private, unlisted instance
“We couldn’t check your rate just now. Nothing was recorded on your file.” Try again → 1.
After Borrow now: a plain reason, no offer pushed. Re-quote me later → 5 stays available.
Reachable from every place. The flow stops selling and routes to a person.
Consent off: nothing is sent. One confirmation, “Reminders are off”, and the journey ends.
Read from the repo on 3 October 2026. Issue and epic numbers point to the evidence on GitHub. The finished state is each epic's own target, as its PRD writes it, not a date.
Epic #279 closed on 14 September 2026 with its hypothesis read as right. The bank holds 76 questions across nine stages, with a 12-question opening set and five facet modules; 12 run packages are committed. Not met at close: auditability, and the audit run found none of eight known gaps, three in part. Epic #504 is open after all 11 committed PRDs, graded on 22 criteria, lacked a status, decision log, summary, launch plan and acceptance criteria.
A discovery session starts in the portal and reaches a PRD in one sitting that an engineer can build from: re-generated PRDs score higher than the committed baseline on the 22 grading criteria, with a decision log and a sign-off line.
The free canvas replaced the fixed grid (#302). Five generic primitives landed: stack, text, list, icon and choice. Import from Brilliant and Figma, the compose loop, ratify, and a ledger of what the agent did, refused and corrected are in place. The first validation run, Faster Payment from its PRD (#316), is still open.
One real flow built from its PRD on the canvas, with one imported part: Faster Payment across four screens, its Confirmation of Payee states, the safety stop and each send outcome, with a handoff pack out the other end.
The pack is generated and committed: 26 component specs, data contracts, tokens for CSS, iOS and Android, Web Component wrappers, the agent vocabulary and an llms.txt index (#419). The backend seam has six open slices, #331 to #336.
A backend engineer wires a real API from the pack with no round trip to design. The PRD counts it as met when a fresh agent run, given only the pack, stands up a service that passes contract-check and logs fewer than three questions for design. The baseline run logged 20.
The repo holds 14 site pages and two prototypes, behind three closed epics (#164, #202, #243). CodeQL's 14 high alerts were fixed in code, none dismissed. Marking what the agent said against what it did (#496) is open.
A public site that replays committed runs, with the gates shown as evidence, and a company brief that compiles to a private, unlisted instance under that company's own pack. No model runs in the reader's browser.
Lines from the job description, each next to where it shows: in ux-factory, or in the prototypes on my portfolio. Kestrel, Meridian and Kettle are invented brands.
Build stores each state as the base screen plus overrides, and a check fails the build when a declared state is missing. In the prototypes, the heavy month prices every way out of a missed payment with nothing pre-selected, and settled names the interest inside an early settlement figure as its own line.
Decide records each decision with its evidence and kill criterion, so product, engineering and design read the same reasons. In agency work I was the primary client contact for most of my time there.
One token contract under every component; token lint fails the build on an undeclared token, and a drift check on a hand-edited generated file. Before this, design systems in Taxi for Email let non-technical marketers assemble on-brand campaigns they could not break.
The handoff pack: component specs and data contracts from one source, tokens for three platforms, and an llms.txt index telling an agent which file to read and when. The Figma import path is documented; the parity run against a real Figma file has not landed.
Agents propose and people decide, and every run is committed. In the handover, an AI support agent reaches a guidance rule and passes the conversation to a person, who reads the briefing and takes the seat.
On my portfolio, contrast is measured per colour pair against its surface, and the measurement can move the brand value. On a separate concept site, outside ux-factory, axe reported 0 violations across 11 routes in light and dark, with 42 text pairs AA by computation. ux-factory itself has no automated accessibility scanner, and none of this is a screen-reader audit.
switch can tell a customer to stay where they are, and faster payment carries a scam stop written to be read. Rate path keeps borrowing now an equal choice. Designed with Consumer Duty in mind.
ugoki, my own native iOS and Android wellness app, 2026. Before that, years of email built mobile first.
The gap, honestly: no moderated user research or usability testing on my record yet. The prototypes can be tested but have not been tested with customers, and their measures are plans, not analytics I have run. No product designer title in a squad, and no employment in an FCA-regulated firm.