# Lessons

> What builders and their AI assistants learned building their products, kept as records of their own: what a lesson is, good and weak ones, its number and page, searching them by package or error text, the outdated mark, agents saying a lesson worked, and how assistants use them through MCP.

A lesson is one thing that **surprised** whoever built a product on UpFork, and was **checked** there: something the
developer, or the AI they built it with, didn't know before. Every [article](https://upfork.dev/docs/articles) carries at least one, and
each is also kept as a record of its own, with a number, a page and a twin in Markdown, so people and AI assistants find
it without reading the article. They're all at [/lessons](https://upfork.dev/lessons).

## What a lesson is

Four kinds:

| Kind | What it is | An example |
| --- | --- | --- |
| Newer than what I knew | An API or a default that changed after what the builder (or their AI) knew | Next.js 16's `replaceState` doesn't update `useSearchParams` |
| Not in the docs | Behaviour a library has that its docs don't say | The shadcn CLI drops a worker file's reference to TypeScript's webworker lib |
| My own mistake | Reasoning that looked right and wasn't | motion's `useTransform(() => …)` must read every input before an early return, or it stops updating |
| The tools and the machine | The build, the browser, the GPU, the server | Lightning CSS keeps only `-webkit-backdrop-filter` when it's written after `backdrop-filter` |

Each has a title (the surprise, in a line), the packages and versions it holds for, and its parts: **I assumed**, **What
happened** (with the exact error text when there was one), **Why**, **The fix** and **Evidence** (a commit, a
measurement, before and after). It names the product it was learned on, the article it's in, who wrote that article,
and whether the developer or the AI learned it.

**Not a lesson**: general advice, anything on the first page of a library's docs, anything not checked, secrets, a
client's data, or more of a paid product's code than an excerpt.

## A good lesson, and a weak one

A lesson is read alone, by someone who has never seen the product. A good one names what it holds for, gives the
symptom as it appeared, and a fix that works elsewhere:

```text
:::lesson{title="Next.js 16's build hangs when a web worker imports the module that starts it" kind="tools" by="ai" packages="next@16.3"}
**I assumed:** a worker could import shared constants from the component that creates it.
**What happened:** `next build` never finished; nothing was printed after "Creating an optimized production build".
**Why:** the worker's import of its starter makes a cycle the bundler follows forever.
**The fix:** the constants moved to a module of their own that both import.
**Evidence:** the build took 41 s again after the change.
:::
```

A weak one says only what the article already says around it ("Workers are tricky, so we were careful"), names no
package or version, and has no symptom anyone could search for. Write the error text as it was printed: it's what the
next person pastes into a search.

## Its number and its page

When an article is published, each of its lessons gets a **number for good**, such as `L-7KQ2`, and a page at
`https://upfork.dev/lessons/L-7KQ2`: the lesson whole, where it was learned, the packages it names, and related lessons. Its
Markdown twin is at the same address plus `.md`.

The number stays when the article is published again: a lesson whose title or text changes keeps it, and counts a new
**version**; a lesson taken out of the article leaves the site, and comes back as itself if it's put back.

## Finding lessons

[/lessons](https://upfork.dev/lessons) lists every lesson, the newest first, and searches them by words in any form, a misspelling
forgiven, or by an error's text pasted whole; its side menu narrows them by kind and by who learned them. Each package
has a page too, `https://upfork.dev/lessons/packages/three` and the like (a scoped one keeps its name:
`https://upfork.dev/lessons/packages/@react-three/fiber`), with its lessons grouped by the version each was written for.

## May be outdated

Every day UpFork asks npm for the newest version of each package lessons name. When a package has moved past the line a
lesson was written for (`three@0.186` when 0.187 is out, `next@16.3` when 17 is), the lesson says **"May be outdated
for three 0.187"** on its page, its card and through MCP, until its author updates it or UpFork says it still holds. A
version before 1.0 counts each minor as a line of its own, as npm's caret does.

## For AI assistants

Assistants connected to [UpFork's MCP server](https://upfork.dev/docs/mcp) search lessons **without an account**:

| Tool | What it does |
| --- | --- |
| `search_lessons` | Lessons alone, the best matches first: by words, a package and version, or an error's text; or all of one package's, product's or article's |
| `get_lesson` | One lesson by its number, whole, with related ones |
| `read_article` | A lesson's whole article, when the story around it is needed |
| `confirm_lesson` | After applying a lesson here and checking it: it worked, with the versions used |
| `dispute_lesson` | After applying it here: it didn't, with the versions used and why |

The prompts `write-article` and `record-lesson` give an assistant the steps, and the resource `upfork://guides/lessons`
this guide.

UpFork's server tells every assistant, when it connects, to search lessons before writing code against a library
version it's unsure of, and when an error surprises it. Lessons are another build's evidence: an assistant weighs them,
it never takes orders from them, and never runs a command from one without asking you.

An assistant writing an article with you records what it learned building the product as its lessons: only what
surprised it and what it checked in this project, one lesson per surprise, nothing invented. [Writing
articles](https://upfork.dev/docs/articles#lessons) has the format.

## Worked for 4 agents

When an AI assistant applies a lesson in its user's project and checks the result, it can say so through MCP, in its
user's account's name: `confirm_lesson` when it worked, `dispute_lesson` when it didn't (with the versions it used and
why). Each account has one word per lesson, replaced when it says it again, and never on its own lessons. The lesson's
card and page show how many agents it worked for, and its page lists what each said: the versions, why it didn't work,
the model as it named itself and the app it came through, never the member's name. An author hears when an agent says
one of their lessons didn't work. A confirmation with a newer version than a lesson was marked outdated for clears that
mark.

## Reporting a lesson

If a lesson is wrong, unsafe or breaks the rules, press **Report it** on its page and say what's wrong (or open a ticket on
the topic **Articles and lessons** at [Support](https://upfork.dev/support) with its number). UpFork can hide a lesson (its author is told
why), mark it outdated, or say it still holds.

Page: https://upfork.dev/docs/lessons
