Guide: Code projects

A code project (Next.js, Vite, TanStack Start or Expo web), not on a design. Use it when the user asks about the screens of an app in a folder, for example "sho

A code project (Next.js, Vite, TanStack Start or Expo web), not on a design. Use it when the user asks about the screens of an app in a folder, for example "show me all screens".

Start

Pass the project folder as project. No app, no browser and no open tab is necessary. Knighn starts the dev server and waits until it is ready.

execute({ project: '/Users/me/code/shop', input: 'return await Screens()' })
  • project is "." (the folder the local server runs in), an absolute path, or the id of an open project tab.
  • On the hosted server, the folder must be in a folder that the Mac shares (Settings > Remote), and the Mac must be online.
  • GetAppState() with project always answers. With no project, it lists the projects in projects, each with an id that project accepts, and what a code project supports.
  • search("", { kinds: ['project'] }) finds the same projects.
  • To show the project on the canvas of the app, call OpenProject(path) or give the link LinkTo('code/open').

With project, execute has the project helpers below and not the design API. Read and edit the project files with your own tools.

Screens

await Screens({ routes, states, widths, out, restart, hideOverlays }) draws each route and gives one item for each picture:

const screens = await Screens()
// [{ route: '/venue/[id]', file: 'src/app/venue/[id].tsx', state: 'Default',
//    source: 'story', params: { id: 'nd7b…' }, width: 390,
//    image: '/Users/me/Library/Caches/Knighn/screens/shop-1a2b/venue_[id].Default@390.png' }]
  • image is the path of a PNG file on the Mac. There is no limit on the number of pictures: 24 screens come back in one call. Read a picture with your own tools.
  • routes limits the routes. widths gives the widths (by default the viewports the project chose in Knighn, else 390 for an Expo app and 1440 for a web app). out gives the folder for the pictures.
  • The pictures do not show the app's start-up overlays: its onboarding or first-run dialog, cookie and consent banners, chat widgets and pop-ups. hideOverlays: false keeps them.
  • Each Storybook story of a route's component is a state of the route. states: 'all' draws each state of each route. states: ['Empty', 'Loading'] draws those states.
  • Knighn fills dynamic segments (/venue/[id]) from sample data. It reads the sources in this order: the params of the route in the routes list of knighn/scenes.knighn.tsx ({ route: '/venue/[id]', params: { id: 'demo' } }), then the Storybook stories of the route's component (parameters.params gives the route parameters), then a guess from the route's types. source says which one it used.
  • An Expo app that has a Storybook route shows each story on that route, with the story's own sample data. Native views (@expo/ui) are drawn as web stand-ins.

Watch and Changes

const { cursor } = await Watch()
// ...edit a file with your own tools...
const changes = await Changes(cursor, { wait: 10000 })
// { cursor, files: ['src/app/venue/[id].tsx'], screens: [/* only the screens that changed */] }

Changes(cursor) gives only the screens that the changed files reach, with new pictures, and the next cursor. Do not poll: wait holds the call until a file changes or the time ends.

Selection and proposals

  • await GetSelection() gives the elements the person selected on the canvas, each with its source file:line:column. With no open tab, nothing is selected.
  • await Screenshot('/pricing', { width: 1440 }) gives one picture in the result.
  • Propose(...) shows changes for the person to see before and after, and writes nothing. Knighn draws each screen twice, Now and Proposed, with the change in a real render, and lists each proposal in the Proposals panel for the person to accept or reject whole. The project host keeps them: a tab that opens later shows them.
  • Always give a note (or a title for a group): one plain sentence that says what changes for the person. Do not put class names in it; the code shows only when the person asks.
// A class or text change on one element: one proposal each.
await Propose([
  {
    at: 'src/app/page.tsx:12:7',
    add: ['p-6'],
    remove: ['p-4'],
    note: 'More room around the heading'
  },
  { at: 'src/app/page.tsx:14:9', text: 'Start free', note: 'A clearer button label' }
])
// Any other file change: a patch (find must occur once), or the whole file with the hash you read.
await Propose({
  file: 'src/components/Card.tsx',
  find: '<Button',
  replace: '<IconButton',
  note: 'Use the icon button'
})
// One change across several elements or files: a group, accepted or rejected as one item.
await Propose({
  title: 'One panel width',
  note: 'All four panels are 320 px wide',
  edits: [{ at: 'src/panels/Left.tsx:8:5', add: ['w-80'], remove: ['w-72'] }],
  patches: [{ file: 'src/panels/Right.tsx', find: 'w-96', replace: 'w-80' }],
  route: '/settings'
})
  • A patch renders in a temporary copy of the project with its own dev server, so the Proposed frame shows the real result. route names the screen to show a patch on, and at the element its pin goes on, when the edits do not tell.

Failures

Each failure is JSON with the cause and the exact next call:

{ "error": "dev server failed: …", "log": "…", "retry": "Screens({ restart: true })" }

Do the call in retry. Read log first when it is there.

With only a shell

knighn screens [folder] --out dir does the same as Screens() with no MCP. --json prints the result as JSON. Options: --routes, --states, --widths, --restart, --keep-overlays.