# How we craft products

We build tools that people can understand, trust and use without fighting
against them. The smallest solution that delivers that outcome is usually
the best one.

This is the shared foundation for Mutative products. Humans and agents read
the same book. It describes the result we expect and leaves room to choose
how to achieve it. A principle should help make a decision. When it only adds
ceremony, change it.

Start with the user’s problem. Keep the solution small. Make its behavior
clear. Handle failure safely. Prove the outcome.

## Start with the outcome

Before choosing an implementation, know what should become possible for the
person using it. Describe the result in terms they could recognize. For an
API or library, that person may be another developer.

“Add an export button” describes a control. “An accountant can download the
filtered transactions and reconcile their exact amounts” describes an
outcome. It tells us what the button must deliver, and what to check.

A useful starting point is one sentence, a sketch or an example response.
Before designing the internals, try the interface as though the feature
already exists: write the command, response or screen and walk through using
it. Check how it fits adjacent features. A small throwaway example can expose
an awkward interaction before it becomes an implementation constraint.

Include the constraints that change the answer: who can act, what data they
need, what must remain private, and what happens if the operation fails.
Ask questions when the answers change the solution. Choose routine details
without turning every step into an approval request.

### Finish a small, useful piece

Choose a scope that can be used and judged as a whole. An export that works
for the current filters and says which records it includes is useful. A row
of buttons for unfinished formats is not.

Use defaults that let people begin with little setup. Reveal advanced
controls when they become relevant. Prefer familiar behavior and remove
choices that do not help someone do their work.

Consider how the change meets the existing product. If the same action is
available from a shared link and a signed-in view, carry the behavior through
both. Preserve genuine differences in permission; avoid accidental
differences in meaning.

State what the release includes and what it leaves out. Do not imply that a
preview covers cases it has never handled. Feedback from actual use should
help decide what to improve next.

## Keep the solution small

Make the code easy for the next teammate to understand and change. Prefer
clear names, explicit data and direct control flow. Put a decision in one
place when several callers need to agree on it.

For example, an interface and a server can share the rule for editing a
draft. The actor here comes from an authenticated session with current
workspace membership, not from the request body:

```js
function canEditDraft(actor, draft) {
  return actor.workspaceId === draft.workspaceId
    && draft.status === "draft"
    && (actor.id === draft.createdBy || actor.role === "owner");
}
```

The interface uses this decision to explain which actions are available.
The server must enforce it too. Sharing a rule reduces disagreement; hiding
a button does not prevent a request.

### Understand what you ship

Be able to explain a shipped system at a whiteboard without reading its code:
how data moves, why this design was chosen over the alternatives, what must
remain true, what a malicious actor could do, and where it fails. Explain the
important data structures and tradeoffs in terms another teammate can follow.

Using an agent does not transfer responsibility for understanding the result.
Tests support that understanding; passing them does not replace it. Disposable
experiments can prioritize learning quickly. Before their code becomes part
of a shipped product, understand and verify it to the same standard.

### Earn each extra part

Use the product’s existing components, tests and libraries when they fit.
Add an abstraction when it removes a real source of complexity. A wrapper
that hides a single obvious call may make the code harder to follow.

Keep responsibilities focused. Remove dead code and obsolete paths as part
of the change that replaces them. Explain intent or a surprising constraint
in a comment when the code cannot express it clearly. Avoid comments that
merely repeat the next line.

The same restraint applies to this handbook, skills and process. Repeated
work may deserve a tool. An isolated task may need only a direct check. A new
folder, skill or document should make future work easier, not give the
current task an appearance of rigor.

### Measure what people wait for

Treat latency and resource use as part of the experience. Measure the paths
a change could make slower, using conditions that resemble actual use.
Record the dataset, environment and whether the run was cold or warm so the
comparison means something.

Remove unnecessary work before adding machinery to make it faster. Fetching,
polling or rendering unused data still has a cost. When a tradeoff is worth
that cost, explain it with evidence.

## Make it feel right

Our products should feel magical to use. Care should be apparent in the
ordinary actions people repeat: a control responds to their touch, a change
is easy to follow, and the next step feels natural. Design the behavior and
the appearance together.

Benji Taylor’s [Family Values](https://benji.org/family-values) is an important
influence: simplicity, fluidity and delight work together. We want that degree
of care throughout a product. A single impressive transition cannot carry a
confusing flow.

### Give the eye a clear path

Use type, spacing and contrast to establish importance. Group things that
belong together; put controls near their consequences. Choose density for
the task. A busy table can be comfortable when its alignment and hierarchy
make comparison easy.

At each step, show where the person is, what they can do and what happens
next. Before a payment, keep the recipient, amount and network together.
Details can sit behind a disclosure; consequences should remain visible.
Use the product’s components and tokens so equivalent interactions agree.

### Lead with the facts

State what happened, what matters and what the person can do next. Use
concrete names, amounts and consequences.

Say each thing once, where it is useful. If a heading, description and banner
repeat the same message, remove the duplication.

Reserve warnings for consequences that affect a decision. Combine warnings
about the same risk. Make important information prominent through hierarchy,
rather than making everything an alert.

Remove filler, self-congratulation and vague reassurance. Every sentence
should help someone understand or act.

Instead of “Warning! Please be aware that this action is irreversible. Are
you absolutely sure you want to proceed?”, use “Delete 12 drafts? They cannot
be recovered.” Label the buttons **Cancel** and **Delete drafts**.

### Respond to the person

Give a button visible feedback as it is pressed, while committing its action
on activation. A drag should follow the pointer without easing behind it.
Keep focus visible and provide keyboard alternatives to gestures.

Distinguish acknowledgment from success. A pressed save button can respond
immediately; “Saved” requires the write to succeed. Preserve input on failure
and show a useful next step. Loading, empty, stale and failed states deserve
the same attention as the happy path.

### Keep the connection visible

Help people recognize what persists through a change. A selected indicator
can travel between options. A detail view can grow from its source. Closing
should make the return to the previous context understandable. Keep unchanged
content stable instead of fading the whole screen again.

Motion can give the product personality: a satisfying settle or a considered
completion moment can make an ordinary action enjoyable. Put that attention
where people interact. Repeated flourishes that interrupt reading or delay
work lose their charm.

### Choose how it moves

Use a short easing curve for a simple change between known states. An ease-out
starts quickly and settles, which suits a control reacting to input. Start
with a small movement, then adjust its distance and duration together in the
actual layout. Larger travel may need more time to remain legible; frequent
actions should settle quickly. The example below uses 180 ms as a starting
point, not a rule for every product.

Use a spring when momentum and repeated redirection matter, such as releasing
a dragged sheet. A spring follows a target while carrying velocity. Damping
controls how much it overshoots; stiffness affects how quickly it responds.
Start with little or no bounce. Reuse a proven implementation and tune it on
the supported devices. A CSS transition is enough for many small controls;
it does not preserve a drag’s release velocity for you.

Never make someone wait for an animation before accepting their next input.
Reverse from the current visible position. Restarting from an endpoint creates
a jump; queuing every click makes the interface fall behind the person.
[Apple’s fluid-interface talk](https://developer.apple.com/videos/play/wwdc2018/803/)
shows why response and interruption belong together.

[Try the motion example](examples/motion.html)

Press and hold an option to see immediate feedback. Switch between CSV and
SQL, then reverse before the indicator arrives. Enable slow motion to inspect
the transition; use Replay for another pass. The format changes immediately
while the indicator catches up. Reduced motion keeps the selection and
feedback, without the travel. This is a local interaction demo, not an export.

The example’s core transition is small:

```css
.indicator {
  transform: translateX(var(--position));
  transition: transform 180ms cubic-bezier(.2, .8, .2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .indicator { transition: none; }
}
```

The radio selection changes `--position`. The same indicator stays mounted,
so CSS can retarget an in-progress transition from its current position.
The [standalone example](examples/motion.html) includes native keyboard controls,
press feedback and the reduced-motion preview.

### Verify the feeling

Use the interaction at normal speed first. Repeat it quickly, reverse it
halfway, and try it with touch and keyboard. For each animation added or
changed, capture it and inspect it frame by frame: the start, movement,
interruption and settled state. Look for jumps, flicker, clipping and
unintended layout shifts. Return to normal speed to judge the rhythm.

Try long labels, real data and a slower supported device; measure dropped
frames when a transition stutters. Prefer transform and opacity when they
express the movement, and measure layout animation when resizing is essential.

With reduced motion enabled, keep the meaning, focus and outcome intact.
Someone should be able to complete the same task without following a moving
surface. Removing travel is often enough; feedback should remain.

Record the interaction and conditions another teammate should try, including
the capture method and what a good result looks like. Automated checks can
establish the selected state, continuity and reduced-motion behavior. Someone
still needs to inspect the frames and use it. A screenshot cannot prove that
an interaction feels good. Clean up the capture session when finished.

## Make failure safe

External input can be missing, malformed, stale or hostile. Validate it at
the boundary before it affects the system. Treat third-party metadata and
agent output as claims to check, not as authority.

Access must be enforced where the action happens. In this illustrative
service function, a denial returns before a write. Authentication, input
validation and loading the current draft happen before calling it:

```js
async function renameDraft(actor, draft, title, store) {
  if (!canEditDraft(actor, draft)) {
    return { status: 403, error: "draft-not-editable" };
  }

  await store.renameIfEditable({
    id: draft.id,
    expectedVersion: draft.version,
    actorId: actor.id,
    title,
  });
  return { status: 200 };
}
```

The store operation must check current permission and version atomically
with the write. Otherwise a draft could change after the first check.
A conflict or storage failure must become an honest failure response at the
service boundary, not a success message.

### Plan for interruption

A retry should not accidentally send a second payment, create a second job
or duplicate a notification. Use an idempotency key or another mechanism
that fits the system. Check what happens when a request succeeds but its
response never reaches the caller.

Keep ordinary actions reversible where practical. For consequential actions
that cannot be undone, make the effect clear before commitment. Preserve
people’s work when something fails. Record consequential changes with enough
context to establish who acted, what changed and when, without logging secrets.

### Give agents explicit authority

An agent is another client of the product. Prefer documented interfaces that
humans can also inspect and use. Give connections only the access they need,
and make that access visible and revocable.

Each product defines which actions an agent may perform and which require a
person. Enforce those boundaries on the server. When people review an
agent’s draft, distinguish its proposal from the verified effect and give
them a way to correct or reject it.

## Prove the outcome

**A change is verified when another teammate can repeat the check and observe
the expected behavior on the current code.**

Start from the agreed outcome and use the smallest check that proves it.
Exercise the relevant interface and observe its consequence. Include the
failure cases the change could affect. A successful build is useful evidence,
but it does not establish that a feature works.

Use the product for actual work as well as controlled checks. Repeated use
reveals defaults you keep changing, unnecessary steps, interruptions and
delays that accumulate across a task. Treat that friction as evidence for
the next improvement.

### Check the consequence

An operator changes their display name. The expected outcome is that the new
name survives a reload. This illustrative browser test assumes an isolated,
signed-in test account, reset to “Grace” before each run, and a profile page
with the labels shown:

```js
import { test, expect } from "@playwright/test";

test("the saved name survives a reload", async ({ page }) => {
  await page.goto("/settings/profile");
  await expect(page.getByLabel("Display name")).toHaveValue("Grace");
  await page.getByLabel("Display name").fill("Ada");
  await page.getByRole("button", { name: "Save", exact: true }).click();
  await expect(page.getByRole("status")).toHaveText("Name saved");

  await page.reload();
  await expect(page.getByLabel("Display name")).toHaveValue("Ada");
});
```

The reload checks more than the success message. Whether it proves database
persistence depends on the environment: an in-memory fixture only proves
behavior against that fixture. A live integration needs evidence from the
integration itself.

### Make the demo prove the claim

Keep each demonstration focused on one claim. Show the starting state,
perform the action, then show its consequence. A persistence demo reopens the
saved work. An export demo inspects the file’s contents. A permission demo
attempts the forbidden write and shows both the refusal and the unchanged
data. A speed demo shows the measurement and its conditions.

Use a workload representative of actual use. Identify simulated data,
unfinished behavior and limits that affect the claim. Keep the relevant
inputs and results visible; explain the design decision briefly. The viewer
should be able to see the evidence and understand how to repeat it.

### Keep proof repeatable

Use existing tests and tools first. For a bug, reproduce the failure before
fixing it when practical, then rerun the check. Keep a regression test when
it will catch the defect again without excessive setup or brittle mocks.

Run the product’s required checks. After further edits, rerun the checks
affected by those edits. Review whether they cover the intended behavior;
green results cannot tell you that you chose the right assertions.

Each product owns its verification commands, fixtures and necessary setup.
Keep useful instructions with the product. A verification skill is worthwhile
when it helps the next person repeat a difficult check. It is not a requirement
for a simple product.

Report what you checked, how to repeat it, and what remains unverified. Link
useful evidence without creating a report for its own sake.

### Finish cleanly

Cleanup is part of completing a change. Remove the code, dependencies, flags
and documentation the change makes obsolete. Resolve temporary workarounds
introduced during the task, or state clearly what remains and why. Keep this
within the change’s scope; preserve unrelated work and useful regression tests.

Stop the servers, browsers, watchers and workers you started, including their
child processes. Track ownership when launching them, then verify they have
exited and released their ports. Do not stop another session’s processes.
A preview may stay open for active user testing if its owner and purpose are
clear; close it when that testing ends.

Remove disposable scripts, logs, frame dumps, captures and test data you
created. Retain only evidence useful for review or reproduction, in a known
location with a clear reason to keep it. An ignored folder is still clutter
if nothing needs its contents. Report any cleanup that could not be completed.

The effort should fit the change. The outcome and the proof matter more than
the ceremony used to produce them.
