# Master Prompting for Cursor: Anchor Your App Extensions with Precision

> Learn to guide Cursor with context and precision, transforming vague prompts into reliable app extensions.
> By Dave · 2026-08-28
> Source: https://otf-kit.dev/blog/how-to-prompt-cursor

## The vague prompt that just ate your afternoon

You asked Cursor to "add a settings page to the dashboard." It generated 800 lines of UI from a hallucinated design system, invented three new components that don't match your existing ones, and rewrote your nav. You spend 40 minutes reverting. The agent wasn't broken — it was unanchored.

Anchoring means giving the model four things before it touches a file: a conventions doc that defines how your code looks, a worked example of a component that already exists in your codebase, a tight scope (one change, named files), and a definition of "done." Do all four and the same prompt becomes a one-shot. Skip them and you're back to revert-and-pray.



![a vague prompt vs an anchored one on the same task](https://cdn.otf-kit.dev/blog/how-to-prompt-cursor/inline-1.png)



## What "vague" actually looks like

Here's a real vague prompt, copy-paste from a Slack thread:

```md
add a settings page with dark mode toggle, notification prefs, and account delete
```

That's it. No conventions, no example, no scope, no done-state. What Cursor does next is determined by training data, not your codebase. It will:

- Pick whichever component shape it last saw in its training mix
- Re-implement a `Switch` from scratch instead of reusing your existing one
- Use a `Card` layout that breaks your existing card grid
- Add a confirmation modal for "delete account" using its own invented pattern

None of that is wrong in isolation. All of it is wrong for *your* app. You didn't get bad code — you got code that wasn't yours.

## The four anchors

Every reliable prompt has four anchors. Think of them as a contract.

```md
1. CONVENTIONS  →  point at CLAUDE.md or .cursorrules
2. EXAMPLE      →  point at one existing file that does something similar
3. SCOPE        →  one change, named files, named symbols
4. DONE         →  what does "done" look like in one sentence
```

If any of those four is missing, the prompt is gambling. The model will fill the gap with whatever it most recently saw, and that's not your codebase.

`CLAUDE.md` and `.cursorrules` are *the* anchor slot for conventions. They live at the repo root and are auto-loaded by Cursor and Claude Code on every request. One file, project-wide context, zero ceremony. A good conventions doc has roughly four sections:

```md
# Project conventions

## Stack
- One component API renders web and native. Same name, same props.
- Tokens flip theme via a single import. Don't hardcode colors.
- Data fetching goes through one client; don't add new helpers.

## Patterns
- Components are forwardRef'd, accept a variants prop, and are named exports.
- Forms use the shared form primitives — don't roll your own.
- Routes live under one config file; add a row, don't invent routing.

## What NOT to do
- Don't add new dependencies without asking.
- Don't change token names. If a token doesn't fit, ask first.
- Don't generate tests for code that doesn't exist yet.
```

That doc is 30 lines. It removes 90% of the guessing. The remaining 10% comes from anchor two.

## Anchor two: a worked example

Cursor is much better at "do this like that" than "do this." Point at one existing file in your codebase that already solves 70% of the problem. The model reads it, copies the shape, and only invents the missing 30%.

Before you write the prompt, open the file you'll reference and read it yourself. If you can't find an example, that's the real task — write the example first, prompt second. Every kit ships with a reference implementation of every component so this anchor is always one click away.

A worked example reference in a prompt reads like this:

```md
Anchor: this row uses the same primitive as <BillingNotificationRow> at
src/components/billing/BillingNotificationRow.tsx — same spacing, same
control layout, same token names. Read it first.
```

That's it. One sentence. The agent now has a template and won't invent one.

## Anchor three: scope, named files, named symbols

"Add a settings page" is a project. "Add a `DarkModeToggle` row to the existing settings page, and wire it to the existing theme provider" is a task. The second one fits in a prompt. The first one fits in a sprint.

A good scoped prompt reads like a function signature:

```md
Scope:
- One new file: src/components/settings/DarkModeToggle.tsx
- One edit: insert the row into src/features/settings/SettingsPage.tsx
  between the existing "Notifications" row and "Account" row
- Wire it to useTheme() from the existing theme provider. Don't add a new
  provider.
```

One file, one edit, one provider. The model has zero room to drift into "and also I'll add a settings route and a context wrapper."

## Anchor four: state what "done" looks like

"Done" is the most-skipped anchor and the most expensive to skip. Without it, the agent picks a finishing point that doesn't match yours — usually "compiles" or "looks reasonable" — and you find out at review time.

A one-sentence done-state changes the output:

```md
DONE: When I import <DarkModeToggle> in the settings page, it renders with the
same visual weight as <BillingNotificationRow>, flips the theme on click, and
the diff is under 80 lines.
```

Now the agent optimizes for *that*. It will pick the right primitive, reuse your existing tokens, and stop at the boundary you set. Without the done-state it would have added a settings route, a context provider, a `useSettings` hook, and probably a migration.

## Before/after: the same task, the same model

Same model, same temperature, same task. The only thing that changed was the prompt.

**Vague:**

```md
add a settings page with dark mode toggle
```

Output: a 600-line diff spanning 14 files, two new dependencies, a context provider, a route, and a `Switch` component built from scratch. Compiled. Shipped nothing useful.

**Anchored:**

```md
Add a <DarkModeToggle> row to the existing settings page at
src/features/settings/SettingsPage.tsx.

Anchor: this row uses the same primitive as <BillingNotificationRow> at
src/components/billing/BillingNotificationRow.tsx — same spacing, same
control layout, same token names. Read it first.

Scope:
- One new file: src/components/settings/DarkModeToggle.tsx
- One edit: add the row between the existing "Notifications" row and
  "Account" row in SettingsPage.tsx
- Wire it to useTheme() from the existing theme provider. Don't add a new
  provider.

Conventions: see .cursorrules for component shape and token usage.

DONE: the toggle renders between Notifications and Account, flips the theme
on click, and the diff is under 80 lines.
```

Output: a 60-line diff. One new file, one edit, reuses the existing primitive, reuses the existing provider, matches the row above and below it. Compiled and shipped.

Same model. Same hardware. The only difference was the prompt.



![how four anchors combine into a single reliable prompt — conventions doc, worked example, ](https://cdn.otf-kit.dev/blog/how-to-prompt-cursor/inline-2.png)



## Iterate on diffs, not rewrites

The last habit is the one that compounds. When the first output isn't right, your instinct is to re-prompt: "no, redo it but…" That tells the model to start over. The previous reasoning — the part that *was* right — gets thrown away with the part that wasn't.

Iterate on the diff instead:

```md
Three things to fix on the diff you just produced:
1. <DarkModeToggle> uses bg-gray-200 directly — replace with bg-surface-2 token
2. Remove the onChange prop, the theme provider already handles persistence
3. The label "Dark mode" should be capitalized "Dark Mode" to match the row above
```

That's a review. The agent fixes the diff instead of rewriting it. Each round gets shorter because the model is operating on a smaller surface. Three rounds of this is faster than one full rewrite, and the output is closer to what you wanted.



![the clay character at a laptop reviewing a small diff, a long stack of reverted commits sh](https://cdn.otf-kit.dev/blog/how-to-prompt-cursor/inline-3.png)



## Why kits ship a context layer

The hard part of agent-driven development isn't the model. It's the *context*. A model with no conventions doc guesses. A model with no worked example invents. A model with no scope rewrites your codebase. The four-anchor prompt above is reliable — but only if the anchors exist in your project already.

That's why every kit ships with the context layer pre-built: a root-level conventions doc for Cursor and Claude Code, repo rules that auto-load on every request, and 20+ tested prompts in `ai/prompts/` that anchor on the kit's own components. You don't write the conventions doc — it's already in the repo. You don't hunt for a worked example — it's the file the prompt points at. The context layer is the part that doesn't change when the model does.

The next model will be faster. The next editor will be smarter. The four anchors won't change.