You built a framework. It has eleven packages, a mixin engine, a schema generator, a CLI, a template renderer, and an Express server. You want an AI to build a dashboard for it.
What do you give the AI?
The wrong answer: everything. Dump the source, the README, the phase specs, the test files. Let the AI figure out what matters. The result is a dashboard that reimplements things the framework already does, makes assumptions about internals that aren’t true, and breaks when the framework changes.
The right answer: the surface. Only what the AI needs to build against the framework — not inside it.
Writing that surface description precisely is a skill. Here’s how it works.
The Core Principle: Consumer, Not Contributor
There are two ways to know a system. A contributor knows how it works. A consumer knows what it exposes.
A dashboard developer is a consumer. They need to know:
- What variables does the framework pass to templates?
- What URLs does it serve?
- Where do their files go?
- What happens when their file exists vs. when it doesn’t?
They do not need to know:
- How MixinEngine walks the prototype chain
- How SchemaGenerator maps field types to Prisma syntax
- How RBAC wildcards are expanded
- What happened in Phase 6
Everything the consumer doesn’t need is noise. Noise makes the AI produce incorrect output — it fills gaps with assumptions, and assumptions about internals are usually wrong.
What to Include
The plug-in mechanism. How does the AI’s output connect to the framework? In DarJS, that’s one paragraph:
The framework serves templates from a
pages/directory. For any route/ui/:modelId, it checks: (1)pages/<modelId>.njk— serve directly; (2)pages/<modelId>.js— load PageDef, render with generic templates; (3) neither — 404. Files you produce replace the generic templates. Drop them inpages/, the framework finds them first.
That’s the entire integration story. Framework internals: zero.
The context variables. What does the AI’s template receive at render time? Be exact — list every variable, its type, its shape. Not “the pageDef” but:
pageDef.id — string, kebab-case model identifier
pageDef.model — string, display name
pageDef.columns — array of column name strings
pageDef.actions — array of permitted action strings, already RBAC-filtered
pageDef.filters — array of filterable field names
pageDef.widgets — [{ type, label, data }]
This is the contract. The AI builds to the contract. If the contract is wrong, the output is wrong. If the contract is incomplete, the AI guesses — and guessing about variable names produces templates that silently render nothing.
The URL conventions. Every HTMX call, every form submission, every delete button needs a URL. List them all:
POST /ui/:modelId → create
PUT /ui/:modelId/:id → update
DELETE /ui/:modelId/:id → delete
POST /ui/:modelId/:id/transition → workflow transition
Without this, the AI invents URLs. Invented URLs look plausible and fail silently.
The data shapes for complex fields. Widget data isn’t just “array of stuff.” Specify it:
line-items — [{ name, quantity, unit_price_ht, tva_rate, line_total_ht }]
payments — [{ method, amount, date, reference }]
stock-alert — { quantity, low_stock_threshold }
The AI uses these shapes to render tables, calculate totals, and build condition checks. A wrong shape means a template that crashes on real data.
What to Omit
Everything not on the consumer surface. Specifically:
Implementation details. How the framework derives columns from mixin metadata, how it maps field types to Prisma, how session tokens are stored — none of this affects template rendering. Including it gives the AI wrong anchors. It might try to call framework internals directly instead of using the provided context variables.
History. The framework was built in eleven phases. The AI doesn’t need to know that. It needs to know what exists now, not how it got there.
Alternatives and tradeoffs. You chose Nunjucks over Handlebars, HTMX over htmx alternatives, SSE over WebSockets. These decisions don’t affect the consumer. Including them invites the AI to revisit them.
Anything already in the stack. If you specify “Tailwind via CDN, Alpine.js via CDN, HTMX via CDN” — that’s the stack. Don’t explain why you chose Tailwind. Don’t explain what Alpine does. The AI knows.
The test for any piece of information: does the AI need this to produce correct output? If no, cut it.
Encoding Design Decisions as Constraints
There’s a category of information that isn’t implementation detail but isn’t surface contract either: design decisions about what the output should do. These go in the constraints section.
Without constraints, the AI makes its own decisions — and they’re often technically correct but wrong for the project. “Render enum fields as <input type='text'>” is technically valid. It’s also wrong for a business app where enums should be <select> dropdowns.
The wizard rule is a good example:
Wizard layout for forms with more than 6 fields. Flat layout for ≤ 6 fields.
That’s a design decision baked in as a constraint. The AI doesn’t have to guess when to use a wizard. It doesn’t have to infer from the data model. The rule is there, it applies mechanically, and the output is consistent.
Other constraints from the DarJS dashboard prompt:
- No JavaScript beyond Alpine.js — prevents the AI from reaching for React
- All interactivity via HTMX + Alpine only — prevents jQuery, vanilla DOM manipulation
- RTL-aware — logical CSS properties,
dirattribute from context - Compact data density — business app, not a marketing site
Each constraint eliminates a class of wrong outputs. The more precise your constraints, the narrower the space of what the AI can produce — and the higher the proportion of that space that’s correct.
The Prompt Is the Interface
A well-written framework prompt is the same thing as a well-designed API. Both describe a surface precisely enough that a consumer can build against it without understanding the internals. Both use contracts (context variables, URL conventions) to make expectations explicit. Both use constraints (no side effects, no extra dependencies) to bound the consumer’s behavior.
The skill transfers. If you can write a good API spec, you can write a good framework prompt. If you can write a good framework prompt, you understand your own API better than you did before writing it.
The act of writing the prompt forces the question: what does a consumer actually need? Every time the answer is “they need to understand an internal,” that’s a signal the abstraction leaks. Fix the framework, then write the prompt.
What You Get
A prompt written this way is model-agnostic. Hand it to GPT-4, Claude, Gemini, a fine-tuned model — any of them can produce correct output because the prompt describes the contract completely and omits everything that would cause confusion.
It’s also version-stable. When the framework changes, you update the prompt to match the new surface. The internal changes are invisible to the prompt, just as they’re invisible to framework consumers.
And it’s reusable. The same prompt produces a Tailwind dashboard today, a DaisyUI dashboard tomorrow, a mobile-optimized version next week — by changing the style direction while keeping the contract description identical.
The framework prompt is the artifact that makes the framework handoff-ready. Without it, every new AI working on the project needs to be onboarded into the internals. With it, any AI can build against the framework the moment you paste the prompt.