feature-map
Skill28 starsChanged 34 days agoMITView on GitHub
Turn a bug report or route into the extension screen it belongs to, its preconditions, the covering Playwright spec and a reproduction. Use when triaging a bug, writing an E2E repro for a report, or adding a route or an rpc handler to the extension (the feature-map check fails until it has an entry).
4 other projects use a version of this.
Adopted by
Most recently taken up by
- onyx-dot-app/onyx-fosstheir own version, first seen Oct 8, 2026
- langwatch/langwatchtheir own version, first seen Oct 8, 2026
- onyx-dot-app/onyxtheir own version, first seen Oct 8, 2026
- markphelps/agent-pluginstheir own version, first seen Sep 28, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- onyx-dot-app/onyx★ 33ksame name
- langwatch/langwatch★ 5ksame name
- onyx-dot-app/onyx-foss★ 320same name
- markphelps/agent-plugins★ 5same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository5 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- leather-changelogSkill
- monorepo-navigationSkill
- testing-patternsSkill
- write-extension-e2eSkill
The file
We split this skill into the rules it gives an agent
6 rules. 0 are common, 0 are distinctive, and 6 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 19
Generate, then find the entry: Run pnpm lt feature-map generate --app extension, then grep extension.md for what you have: a route …
- Very specific: line 34
Every entry declares what it is anchored on.
- Very specific: line 65
Run pnpm --filter @leather.io/tools build first if pnpm lt reports an unknown command.
- Very specific: line 69
Every routed RouteUrls key and every rpc handler file must be claimed by an entry.
- Very specific: line 93
Write "back goes to the previous page" when the header back button calls navigate(-1), and say when a control opens a full-page tab from …
- Very specific: line 112
After editing a source run check, then generate to refresh your local extension.md.
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Feature map
pnpm lt feature-map generate --app extension writes extension.md next to this file: a generated
inventory of the extension, one entry per screen, dialog, dApp method or behavior a user would name.
The file is gitignored, so generate it at the start of a session (a few seconds; the first run also
builds the tools package). Each entry resolves its anchor, routes or opener,
gate, feature flags, params, entry steps, preconditions, selectors, page-object actions and covering
specs. The sources are typed TypeScript in
apps/extension/tests/feature-map/*.features.ts; they import the real RouteUrls and
*Selectors objects, so a rename breaks pnpm --filter @leather.io/extension typecheck.
From a report to a reproduction
- Generate, then find the entry. Run
pnpm lt feature-map generate --app extension, then grepextension.mdfor what you have: a route (/send/btc), aRouteUrlskey, a selector, or words from the description of what the screen shows. - Read the entry. Routes shows where the router mounts it, which gate wraps it and which LaunchDarkly flags must be on; a dialog or sheet shows Opened by instead: the start route and the page-object action that reveals it. Preconditions gives the session state, network, account type and the mock functions the specs register. Entry lists the click path and deep link.
- Open the covering spec and action. Specs are repo-relative paths; Actions link to the
page-object method that owns the readiness wait for that screen (see
write-extension-e2e). Copy the spec'sbeforeEach, call the listed actions, then assert on the listed selectors. - Write the repro following the
write-extension-e2eskill. Never bypass a page-object action with a raw selector from the entry; the action waits for the data the screen validates against.
Anchors
Every entry declares what it is anchored on. The check enumerates routed RouteUrls keys and rpc
handler files and fails when one has no entry; selectors and behavior entries are optional.
anchor | Use for | Enumerated from | Claimed by |
{ kind: 'route' } | a screen with a URL | RouteUrls keys the routers reference | the entry's routes |
{ kind: 'selectors', objects: [...] } | a dialog, sheet, banner or badge with no URL | exported *Selectors objects in apps/extension/tests/selectors | objects (whole object 'SwitchAccountSelectors' or members 'HomePageSelectors.NetworkSwitcher') |
{ kind: 'rpc', handlers: [...] } | a dApp method whose handler opens no approver (open, openSwap, supportedMethods) | file basenames in apps/extension/src/background/messaging/rpc-methods | handlers, or a routed entry whose entry[].method the handler registers |
{ kind: 'behavior' } | something the wallet does with no screen (wallet lock, store migrations, the Ledger flow steps) | nothing; hand-authored | — |
Selector objects are not enforced: a dialog rendered inside a routed screen can live in that
screen's entry, and a selectors entry is added when a dialog needs one of its own. Popup-opening
rpc methods keep their route anchor; their
entry[].method claims the handler. Behaviors are listed under Behaviors in extension.md
and skip the gate cross-check; everything else they reference (specs, actions, selectors, mocks) is
still validated.
Entries without a route carry startRoute (a RouteUrls value, default /) and open: the
page-object action or sequence of actions ('HomePage.goToReceiveDialog',
['SwitchAccountPage.open']) that reveals the screen from the start route. Any public
page-object method can be used. Leave open empty when no action reveals the screen (a
form must be filled, a storage key set); the specs are then the reproduction. A routed entry may
also set open when the screen only renders after an action from its route.
Commands
pnpm lt feature-map generate --app extension # write .claude/skills/feature-map/extension.md (gitignored; run before reading)
pnpm lt feature-map check # CI gate: entries, references, route and rpc coverageRun pnpm --filter @leather.io/tools build first if pnpm lt reports an unknown command.
Adding or changing an entry
- Every routed
RouteUrlskey and every rpc handler file must be claimed by an entry. A nested sheet (edit-tx-nonce,recipient-accounts) is listed in theroutesof the feature whose route mounts it. specs,actionsandopen(PageObject.method),preconditions.mocks(mock functions exported fromtests/mocks) andentry[].method(an RPC method string) must exist;flagsmust be keys ofFeatureFlags;anchor.objectsmust name exported*Selectorsobjects or members andanchor.handlersfiles underrpc-methods.selectors.readyis the element whose presence means the screen is usable; it is required for route and selectors anchors and may be omitted for rpc anchors without UI and for behaviors.othersare the elements a repro will touch on this screen; the control on the previous screen that opens it belongs in theentrysteps. Both are*Selectors.Memberreferences, never raw strings, and both must render on this screen in the precondition state (say in the description when one is conditional).preconditions.mockslists the mock functions that take only a page or context handle: alwayssetupMockApisandmockUnhandledRequests(whatglobalPage.setupAndUseApiCallsregisters), plus the ones the screen's default state needs on top. A mock thatsetupMockApisalready calls fails the check when listed next to it. A variant mock only some covering specs register (mockMixedUtxosForSend) and a mock that needs data arguments (mockLeatherBitcoinTransactions,mockMixedUtxoRequests,mockBnsV2NameLookup,mockFundedStacksAddress,overrideLaunchDarklyFlags, …) is named in the description together with the spec that registers it and what it changes on the screen.preconditions.networkis set whenever a covering spec switches network or the screen requires one (swap needs mainnet; most send specs run on testnet4).- Descriptions state only what a component, hook or spec shows, with UI strings quoted verbatim.
Write "back goes to the previous page" when the header back button calls
navigate(-1), and say when a control opens a full-page tab from the popup (Buy, Sell, Send on Ledger); specs run full-page, so they never show that branch. gatemust match what the router mounts the route behind (the innermost of AccountGate, OnboardingGate, LedgerStacksGate, LedgerBitcoinGate, ornone); the check compares it per mounted path for route anchors, so an entry whose routes sit behind different gates has to be split. For other anchors it documents the gate of the screen that hosts the dialog.entryfor an RPC popup iskind: 'rpc'with themethodstring the dApp calls; the steps say how the test app at localhost:3000 triggers it and that the popup is awaited withcontext.waitForEvent('page').deepLinkable: falsemarks a screen that reads router state the previous screen passes (the send confirmations, the sent summaries, the BTC fee sheet, the swap review): a bare URL redirects or throws, soextension.mdsays so and the specs are the reproduction.specs: []is allowed; those entries are listed under Uncovered screens inextension.mdso E2E work can target them.- Whenever a spec, page object, mock or selector under
apps/extension/testschanges, or the screen a spec covers changes, proofread the entries that reference it (grep the file path or selector in*.features.ts) and fix their steps, preconditions and description. The check validates references only; wrong prose passes it. - After editing a source run
check, thengenerateto refresh your localextension.md; the file is gitignored and never committed. - The check validates references, not prose. Every Monday
repo:feature-map-fixreads every entry against the source (.github/prompts/feature-map-fix.md), fixes the statements it can prove false and opens or updates thebot/feature-map-fixpull request with a per-row citation; it closes that PR when a run finds nothing. Re-read the cited lines before merging it.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

