ucode-agent 1.26.1 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,13 +137,21 @@ model fixes them without being told.
137
137
  hard-linked into the next app — the same files under another name, so it costs
138
138
  no extra disk and skips the wait entirely.
139
139
 
140
- **Apps start from a ready-made starter.** Setting up Next.js and shadcn from
141
- nothing takes about four minutes — `create-next-app` and the shadcn CLI measured
142
- at 116s and 130s — plus a dozen model round trips. `create_app` copies ucode's
143
- starter instead: Next.js 16, TypeScript, Tailwind 4, shadcn/ui with 25 common
144
- components, light/dark mode, toasts and a considered theme, already known to
145
- build. The copy takes under a second, and its install runs in the background
146
- while the model writes the first components.
140
+ **Apps start from a ready-made starter — and finish in the same call.**
141
+ `create_app` copies a starter that is already known to build, and takes the
142
+ app's files with it, so a one-page app is a single round trip: the starter
143
+ lands, the model's files are written over it, and the starter's own files come
144
+ back inside the result so there is nothing to read afterwards.
145
+
146
+ The default starter is `plain-html`: one page, one stylesheet, one module,
147
+ nothing to install and nothing to build. A tasks app, a game, a calculator or a
148
+ visualisation is finished before a framework would have finished installing.
149
+ `next-shadcn` is there for routes, a database or many screens — Next.js 16,
150
+ TypeScript, Tailwind 4, shadcn/ui with 25 components, light/dark, toasts and a
151
+ considered theme. Setting that up by hand is about four minutes
152
+ (`create-next-app` and the shadcn CLI measured at 116s and 130s) plus a dozen
153
+ round trips; the copy takes under a second, and its install runs in the
154
+ background while the model writes the first components.
147
155
 
148
156
  **Built to be fast, and measured.** A traced build of a small Next.js app went
149
157
  from 17 minutes and 116 model steps to about 6 minutes and 25 steps, by fixing
@@ -161,10 +169,31 @@ where the time actually went:
161
169
  command fixes it.
162
170
  - The starter is the shadcn models already know (Radix), so the code they write
163
171
  compiles the first time.
172
+ - A new app goes out without the tools it has nothing to point at — no symbol
173
+ lookup, no rename, no type query in an empty folder — and an instruction pack
174
+ that loads itself sends its short form, with the full one a `load_skill`
175
+ away. Both are re-read by the provider on every step, so what is not in the
176
+ request is time off every one of them.
177
+ - Ready-made blocks for a page with no build step as well as for React: a list you
178
+ can add to, tick off, rename and remove, a filter row, a localStorage store, a
179
+ dialog, toasts, a theme toggle. Typing is the slowest part of a build, and each
180
+ block is a hundred lines nobody has to type.
181
+ - A nested argument written the wrong way — a JSON string, a { path: contents } map
182
+ — is read rather than refused. Each refusal was a round trip spent being told
183
+ something that could simply be parsed.
184
+ - A tool that was not offered is refused rather than quietly run, so withholding one
185
+ from a new project, or from plan mode, means what it says.
186
+ - The closing message is cut to eight lines. A build that ends with the request
187
+ read back and every feature ticked off is a status report nobody asked for,
188
+ and it is the last thing left on screen.
164
189
 
165
190
  `UCODE_TRACE=1` writes every model call and tool, with its duration, to
166
191
  `~/.ucode/trace.jsonl`.
167
192
 
193
+ Measured on "build me a simple todo app" — same prompt, same model, two traced
194
+ runs: **27 model calls, 8 failed tool calls, no finished app** before this round of
195
+ work; **14 model calls, no failures, a working app in under two minutes** after it.
196
+
168
197
  **Deploy in one line.** Say "deploy it", or type `/deploy [folder]`, and the app
169
198
  goes live on Vercel. ucode picks a short project name that fits the app and is
170
199
  free (`food-iq`, else `food-iq-app`…), copies the app's `.env` keys to Vercel as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ucode-agent",
3
- "version": "1.26.1",
3
+ "version": "1.27.0",
4
4
  "description": "ucode - a terminal coding agent that reads, edits and runs your code, on NVIDIA and Cohere models.",
5
5
  "type": "module",
6
6
  "main": "ucode.js",
@@ -0,0 +1,94 @@
1
+ # Building something from nothing — the short form
2
+
3
+ The failure mode is not bad code. It is a folder of files that has never been
4
+ run, handed over as if it works.
5
+
6
+ ## 1. Decide the shape before any file exists
7
+
8
+ One line each: **what it does**, **the core loop** (the one path that must work
9
+ perfectly), **the stack**, **the file list**.
10
+
11
+ | Need | Choose |
12
+ | --- | --- |
13
+ | One page, no secrets, no server | `create_app` with `plain-html` |
14
+ | Interactive client app, no secrets | `plain-html` still, unless it truly needs a build |
15
+ | Pages plus a server, secrets, API routes, SEO | `create_app` with `next-shadcn` |
16
+ | An API on its own | Node (Hono/Express) or Python (FastAPI) |
17
+
18
+ Pick the smallest one that does the job and mean it: a tasks app, a
19
+ calculator, a timer, a game, a visualisation — all one page. Next.js costs an
20
+ install and a build, minutes the user waits through, and buys nothing an app
21
+ with no server needs.
22
+
23
+ ## 2. Start from the starter — and finish in the same call
24
+
25
+ `create_app` takes `files`, so for a one-page app the scaffold and the whole
26
+ app are one call:
27
+
28
+ ```
29
+ create_app({ folder: "tide", name: "Tide", files: [
30
+ { path: "tide/index.html", content: "…" },
31
+ { path: "tide/styles.css", content: "…" },
32
+ { path: "tide/app.js", content: "…" },
33
+ ]})
34
+ ```
35
+
36
+ Every round trip is ten to forty seconds of the user's time, so one call
37
+ instead of four is most of how long the build takes.
38
+
39
+ - `plain-html` is the default: three files, no install, no build. Its files
40
+ come back in full inside the result — **never read them back**.
41
+ - `next-shadcn` installs in the background; commands in that folder wait for
42
+ it on their own, so start writing components at once. Re-tint `globals.css`
43
+ for the app's direction rather than shipping the slate default.
44
+ - Never run `create-next-app` or `shadcn init`. Nothing you run has a
45
+ keyboard: every scaffolder needs its answers as flags up front.
46
+
47
+ ## 2b. Do not type what already exists
48
+
49
+ `add_block` has the pieces every app needs, written for whichever starter this
50
+ one uses: a list you can add to, tick off, rename and remove; a filter row; a
51
+ localStorage store; a dialog; toasts; a theme toggle; a table; an empty state.
52
+ Call it before writing any of those by hand. Each is a hundred lines you skip,
53
+ and typing is the slowest part of a build — a page assembled from blocks is
54
+ done minutes before the same page typed out. Call `add_block` with no name to
55
+ see what fits this app.
56
+
57
+ ## 3. Structure
58
+
59
+ One component per file, named for what it is, not a 600-line `page.tsx`. In
60
+ Next.js: `src/app` (routes, `globals.css`, `api/<name>/route.ts`),
61
+ `src/components/<feature>/`, `src/lib/` for outside services and schemas.
62
+ Server components by default, `"use client"` only where it is interactive.
63
+ Types at every boundary; parse external data rather than trusting its shape.
64
+
65
+ ## 4. Secrets and outside services
66
+
67
+ - **A key never reaches the browser.** It lives in a server route. Anything
68
+ imported by a `"use client"` file ships to every visitor, including a
69
+ "hardcoded for now" key — put it in a server-only module and say where.
70
+ - Every outbound call gets a timeout (`AbortSignal.timeout(60_000)`), a status
71
+ check, and an error that says what failed — surfaced as a real message,
72
+ never a silent `catch {}`.
73
+ - Calling a model: ask for JSON and parse it defensively (extract the first
74
+ `{...}`, validate, clamp numbers), put the judgement rules in the prompt
75
+ explicitly, and make the route timeout longer than the model takes.
76
+
77
+ ## 5. Build order
78
+
79
+ Skeleton and design tokens first, so everything after is styled correctly the
80
+ first time; then the server route with the real integration; then the core
81
+ loop UI wired to it; then every state — empty, loading, success, error,
82
+ invalid input; then polish: motion, responsive, copy, title and metadata.
83
+
84
+ ## 6. Prove it works, then report
85
+
86
+ `npm run build` type-checks and lints — a build that fails is not done. Start
87
+ it (`npm run dev` backgrounds itself and returns the URL; do not start it
88
+ twice), then `look_at_app` on every page. A clean build proves it compiles,
89
+ not that it works. Fix what you find and check again.
90
+
91
+ Done means: the core loop works end to end, no TODO, no placeholder copy, no
92
+ dead buttons, no console errors, every async action has its states, secrets
93
+ server-side, build passes. Then say what you built, how to run it, and — in
94
+ one sentence — anything you did not finish or could not test.
@@ -1,181 +1,222 @@
1
- ---
2
- name: build-app
3
- description: Take an app from nothing to running and finished — stack choice, non-interactive scaffolding, project structure, secrets, AI and API integration, error handling, and proving it works before saying it does.
4
- auto: scaffold, new project, from scratch, build an app, make an app, create an app, build a website, make a website, build a site, build me a, next.js app, nextjs app, next app, react app, vite app, shadcn, create-next-app, full stack, fullstack, saas, mvp
5
- ---
6
-
7
- # Building something from nothing
8
-
9
- The failure mode is not bad code. It is a folder of files that has never been
10
- run, handed over as if it works. Everything here is ordered to prevent that.
11
-
12
- ## 1. Decide the shape, out loud, before any file exists
13
-
14
- One line each:
15
-
16
- - **What it does** — the sentence a user would say.
17
- - **The core loop** — the one path through it that must work perfectly
18
- (e.g. upload a photo → analysed → see a score and the problems).
19
- - **The stack, and why** — the smallest thing that does the job:
20
-
21
- | Need | Choose |
22
- | --- | --- |
23
- | One page, no secrets, no server | a single `index.html`, no build step |
24
- | Interactive client app, no secrets | Vite + React + TypeScript |
25
- | Pages plus a server, secrets, API routes, SEO | Next.js App Router + TypeScript |
26
- | An API on its own | Node (Hono/Express) or Python (FastAPI) |
27
-
28
- - **The file list** — the whole tree, before creating any of it.
29
-
30
- If there is a user interface, the `ui-ux` skill is already loaded. Decide the
31
- design direction now, not after the logic works. If the app calls a model,
32
- load `ai-features`; if it has accounts, keys or uploads, load `security`.
33
-
34
- ## 2. Start from the starter
35
-
36
- **For a Next.js app, call `create_app`** — one step, about a second:
37
-
38
- ```
39
- create_app({ folder: "my-app", name: "My App", description: "…" })
40
- ```
41
-
42
- It copies ucode's ready-made starter — Next.js 16, TypeScript, Tailwind 4,
43
- shadcn/ui with 25 common components, light/dark mode, toasts, a considered
44
- theme — which is already known to build, and starts `npm install` in the
45
- background. Read the `TEMPLATE.md` it lists, then start writing components
46
- straight away; commands in that folder wait for the install on their own.
47
- Re-tint the palette in `globals.css` and swap the font for the app's direction.
48
-
49
- Never run `create-next-app` or `shadcn init` for a Next.js app — that is
50
- four minutes and a dozen steps the starter already did.
51
-
52
- ### Other stacks
53
-
54
- Nothing you run has a keyboard. A scaffolder that asks "Would you like to use
55
- TypeScript?" gets no answer and fails, so give it every answer up front:
56
-
57
- ```bash
58
- # Next.js into ./my-app (use . to fill the current folder — it must be empty)
59
- npx create-next-app@latest my-app --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes
60
-
61
- # shadcn/ui, from inside the project — every component you need, in one add
62
- npx shadcn@latest init -d -y
63
- npx shadcn@latest add button card input label badge progress separator skeleton sonner tooltip -y
64
- ```
65
-
66
- - `create-next-app` refuses a folder that already has files. If the current
67
- folder is not empty, scaffold into a named subfolder and pass it as `cwd` to
68
- every later command.
69
- - For any other scaffolder, find the flag for every question (`--help`) first.
70
- - Install dependencies once, all together: `npm i zod lucide-react` — not one
71
- `npm i` per package.
72
-
73
- ## 3. Structure it like a real project
74
-
75
- For Next.js App Router:
76
-
77
- ```
78
- src/
79
- app/
80
- layout.tsx fonts, metadata, <body> shell, Toaster
81
- page.tsx the screen — composes components, holds little logic
82
- globals.css design tokens and the shadcn theme variables
83
- api/<name>/route.ts server-only endpoints; the only place secrets live
84
- components/
85
- <feature>/ one folder per feature: its pieces, split by job
86
- ui/ shadcn components (generated — edit via the theme)
87
- lib/
88
- <service>.ts calls to outside services, typed in and out
89
- schemas.ts zod schemas shared by client and server
90
- utils.ts
91
- types/ shared TypeScript types, if lib/ does not own them
92
- ```
93
-
94
- - **One component per file**, named for what it is (`ScoreDial.tsx`,
95
- `NutrientFindings.tsx`, `LabelUpload.tsx`), not a 600-line `page.tsx`.
96
- - Server components by default; `"use client"` only on the interactive parts.
97
- - Types at every boundary. Parse external data with zod rather than trusting
98
- its shape.
99
-
100
- ## 4. Secrets and outside services
101
-
102
- - **A key never reaches the browser.** It lives in a server route or server
103
- action. Anything imported by a `"use client"` file ships to every visitor —
104
- including a "hardcoded for now" key. If the user asks to hardcode one, put it
105
- in a server-only module (`lib/server/*.ts`, or `import 'server-only'`) and
106
- say where it is so they can move it to `.env.local` later.
107
- - Every outbound call gets: a timeout (`AbortSignal.timeout(60_000)`), a check
108
- of the response status, and an error that says what failed — surfaced to the
109
- UI as a real message, never a silent `catch {}`.
110
-
111
- ### Calling an AI model (any OpenAI-compatible API)
112
-
113
- ```ts
114
- // src/app/api/analyze/route.ts — runs on the server only
115
- export const runtime = 'nodejs';
116
- export const maxDuration = 60;
117
-
118
- const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
119
- method: 'POST',
120
- headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
121
- body: JSON.stringify({
122
- model: 'provider/model-id',
123
- messages: [
124
- { role: 'system', content: 'Reply with JSON only, matching this shape: {...}' },
125
- { role: 'user', content: [
126
- { type: 'text', text: 'Analyse this nutrition label.' },
127
- { type: 'image_url', image_url: { url: dataUrl } }, // data:image/jpeg;base64,...
128
- ] },
129
- ],
130
- }),
131
- signal: AbortSignal.timeout(60_000),
132
- });
133
- ```
134
-
135
- - **Ask for JSON and parse it defensively.** Models wrap JSON in prose or code
136
- fences: extract the first `{...}` block, `JSON.parse` it, validate with zod,
137
- and on failure return a clear "could not read the result" error rather than
138
- crashing. Clamp numbers to their range.
139
- - **Put the judgement rules in the prompt, explicitly** — thresholds, what
140
- counts as "too much", what to omit. A vague prompt gives a different answer
141
- every time; a specific one gives the product its consistency.
142
- - **Images:** check type and size on the client (e.g. ≤ 5 MB, jpeg/png/webp),
143
- downscale large photos in a canvas before upload, send as a base64 data URL.
144
- - Reasoning models may take 10–60s. Show progress, and make the route's
145
- timeout longer than the model's.
146
-
147
- ## 5. Build order
148
-
149
- 1. Skeleton and design tokens, so every later piece is styled correctly first time.
150
- 2. The server route with the real integration, tested with `curl` before any UI.
151
- 3. The core loop UI, wired to the real route.
152
- 4. Every state: empty, loading, success, error, and invalid input.
153
- 5. Polish: motion, responsive, copy, favicon, page title and metadata.
154
-
155
- Use `batch_write` for the skeleton — one call, every file.
156
-
157
- ## 6. Prove it works
158
-
159
- - `npm run build` — it type-checks and lints; a build that fails is not done.
160
- - Start it: `npm run dev` goes to the background on its own and comes back with
161
- the URL once ready. Do not start it twice.
162
- - Exercise it: `curl` the API route with real input, then `look_at_app` on every
163
- page — it loads them in a real browser at phone and desktop width and reports
164
- errors, overflow and a visual review. A clean build proves it compiles, not
165
- that it works.
166
- - Fix what you find and check again.
167
-
168
- ## 7. Definition of done
169
-
170
- - The core loop works end to end against the real service.
171
- - No `TODO`, no placeholder copy, no dead buttons, no console errors.
172
- - Every async action has loading, success and error states.
173
- - Invalid input is caught with a useful message before it reaches the server.
174
- - Secrets only on the server.
175
- - `npm run build` passes.
176
-
177
- ## 8. Report
178
-
179
- What you built, the URL, how to start it again, and what you checked. If
180
- anything is untested or unfinished, name it — one sentence of honesty saves the
181
- user an hour of finding out on their own.
1
+ ---
2
+ name: build-app
3
+ description: Take an app from nothing to running and finished — stack choice, non-interactive scaffolding, project structure, secrets, AI and API integration, error handling, and proving it works before saying it does.
4
+ auto: scaffold, new project, from scratch, build an app, make an app, create an app, build a website, make a website, build a site, build me a, next.js app, nextjs app, next app, react app, vite app, shadcn, create-next-app, full stack, fullstack, saas, mvp
5
+ ---
6
+
7
+ # Building something from nothing
8
+
9
+ The failure mode is not bad code. It is a folder of files that has never been
10
+ run, handed over as if it works. Everything here is ordered to prevent that.
11
+
12
+ ## 1. Decide the shape, out loud, before any file exists
13
+
14
+ One line each:
15
+
16
+ - **What it does** — the sentence a user would say.
17
+ - **The core loop** — the one path through it that must work perfectly
18
+ (e.g. upload a photo → analysed → see a score and the problems).
19
+ - **The stack, and why** — the smallest thing that does the job:
20
+
21
+ | Need | Choose |
22
+ | --- | --- |
23
+ | One page, no secrets, no server | `create_app` with the `plain-html` starter |
24
+ | Interactive client app, no secrets | `plain-html` still, unless it truly needs a build |
25
+ | Pages plus a server, secrets, API routes, SEO | `create_app` with `next-shadcn` |
26
+ | An API on its own | Node (Hono/Express) or Python (FastAPI) |
27
+
28
+ Choose the smallest one that does the job, and mean it: a tasks app, a
29
+ calculator, a timer, a game, a visualisation, a converter — all one page.
30
+ Next.js costs an install and a build, minutes the user waits through, and
31
+ buys nothing an app with no server needs.
32
+
33
+ - **The file list** — the whole tree, before creating any of it.
34
+
35
+ If there is a user interface, the `ui-ux` skill is already loaded. Decide the
36
+ design direction now, not after the logic works. If the app calls a model,
37
+ load `ai-features`; if it has accounts, keys or uploads, load `security`.
38
+
39
+ ## 2. Start from the starter — and finish in the same call
40
+
41
+ `create_app` copies a starter that is already known to build. It also takes
42
+ `files`, so for a one-page app the scaffold and the whole app are one call:
43
+
44
+ ```
45
+ create_app({
46
+ folder: "tide", name: "Tide", description: "…",
47
+ files: [
48
+ { path: "tide/index.html", content: "…" },
49
+ { path: "tide/styles.css", content: "…" },
50
+ { path: "tide/app.js", content: "…" },
51
+ ],
52
+ })
53
+ ```
54
+
55
+ That is the whole build. Every round trip is ten to forty seconds of the
56
+ user's time, so the difference between one call and four is most of how long
57
+ this takes.
58
+
59
+ - **`plain-html` is the default**: three files, no install, no build, opens in
60
+ a browser. Its files come back in full inside the result — **never read them
61
+ back**, they are already in front of you.
62
+ - **`next-shadcn`** only when the app needs routes, a database or many
63
+ screens. It copies Next.js 16, TypeScript, Tailwind 4 and shadcn with its
64
+ components, light/dark and toasts, and starts `npm install` in the
65
+ background — commands in that folder wait for the install on their own, so
66
+ start writing components at once. Read the `TEMPLATE.md` in the result, and
67
+ re-tint `globals.css` for the app's direction.
68
+
69
+ Never run `create-next-app` or `shadcn init` — that is four minutes and a
70
+ dozen steps the starter already did.
71
+
72
+ ### Other stacks
73
+
74
+ Nothing you run has a keyboard. A scaffolder that asks "Would you like to use
75
+ TypeScript?" gets no answer and fails, so give it every answer up front:
76
+
77
+ ```bash
78
+ # Next.js into ./my-app (use . to fill the current folder — it must be empty)
79
+ npx create-next-app@latest my-app --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm --yes
80
+
81
+ # shadcn/ui, from inside the project — every component you need, in one add
82
+ npx shadcn@latest init -d -y
83
+ npx shadcn@latest add button card input label badge progress separator skeleton sonner tooltip -y
84
+ ```
85
+
86
+ - `create-next-app` refuses a folder that already has files. If the current
87
+ folder is not empty, scaffold into a named subfolder and pass it as `cwd` to
88
+ every later command.
89
+ - For any other scaffolder, find the flag for every question (`--help`) first.
90
+ - Install dependencies once, all together: `npm i zod lucide-react` — not one
91
+ `npm i` per package.
92
+
93
+ ## 2b. Do not type what already exists
94
+
95
+ `add_block` has the pieces every app needs, written once and carefully, for
96
+ whichever starter this app uses. For a plain page: `item-list` (add, tick off,
97
+ rename, remove, empty state, counts, keyboard), `filter-bar`, `store`
98
+ (localStorage that survives private mode and stays in step across tabs),
99
+ `modal`, `toast`, `theme-toggle`. For React: `app-shell`, `page-header`,
100
+ `empty-state`, `data-table`, `stat-cards`.
101
+
102
+ Call it before writing any of those by hand. Two reasons, and the second is
103
+ the one that gets forgotten:
104
+
105
+ - They are finished. Every state, the keyboard, small screens, the cases a
106
+ first draft skips.
107
+ - They are already typed. Typing is the slowest part of a build — a few
108
+ thousand tokens at forty a second — so a page assembled from blocks is done
109
+ minutes before the same page written out line by line.
110
+
111
+ They land as ordinary source files. Edit them to suit the app rather than
112
+ working around them.
113
+
114
+ ## 3. Structure it like a real project
115
+
116
+ For Next.js App Router:
117
+
118
+ ```
119
+ src/
120
+ app/
121
+ layout.tsx fonts, metadata, <body> shell, Toaster
122
+ page.tsx the screen — composes components, holds little logic
123
+ globals.css design tokens and the shadcn theme variables
124
+ api/<name>/route.ts server-only endpoints; the only place secrets live
125
+ components/
126
+ <feature>/ one folder per feature: its pieces, split by job
127
+ ui/ shadcn components (generated — edit via the theme)
128
+ lib/
129
+ <service>.ts calls to outside services, typed in and out
130
+ schemas.ts zod schemas shared by client and server
131
+ utils.ts
132
+ types/ shared TypeScript types, if lib/ does not own them
133
+ ```
134
+
135
+ - **One component per file**, named for what it is (`ScoreDial.tsx`,
136
+ `NutrientFindings.tsx`, `LabelUpload.tsx`), not a 600-line `page.tsx`.
137
+ - Server components by default; `"use client"` only on the interactive parts.
138
+ - Types at every boundary. Parse external data with zod rather than trusting
139
+ its shape.
140
+
141
+ ## 4. Secrets and outside services
142
+
143
+ - **A key never reaches the browser.** It lives in a server route or server
144
+ action. Anything imported by a `"use client"` file ships to every visitor —
145
+ including a "hardcoded for now" key. If the user asks to hardcode one, put it
146
+ in a server-only module (`lib/server/*.ts`, or `import 'server-only'`) and
147
+ say where it is so they can move it to `.env.local` later.
148
+ - Every outbound call gets: a timeout (`AbortSignal.timeout(60_000)`), a check
149
+ of the response status, and an error that says what failed — surfaced to the
150
+ UI as a real message, never a silent `catch {}`.
151
+
152
+ ### Calling an AI model (any OpenAI-compatible API)
153
+
154
+ ```ts
155
+ // src/app/api/analyze/route.ts — runs on the server only
156
+ export const runtime = 'nodejs';
157
+ export const maxDuration = 60;
158
+
159
+ const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
160
+ method: 'POST',
161
+ headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
162
+ body: JSON.stringify({
163
+ model: 'provider/model-id',
164
+ messages: [
165
+ { role: 'system', content: 'Reply with JSON only, matching this shape: {...}' },
166
+ { role: 'user', content: [
167
+ { type: 'text', text: 'Analyse this nutrition label.' },
168
+ { type: 'image_url', image_url: { url: dataUrl } }, // data:image/jpeg;base64,...
169
+ ] },
170
+ ],
171
+ }),
172
+ signal: AbortSignal.timeout(60_000),
173
+ });
174
+ ```
175
+
176
+ - **Ask for JSON and parse it defensively.** Models wrap JSON in prose or code
177
+ fences: extract the first `{...}` block, `JSON.parse` it, validate with zod,
178
+ and on failure return a clear "could not read the result" error rather than
179
+ crashing. Clamp numbers to their range.
180
+ - **Put the judgement rules in the prompt, explicitly** — thresholds, what
181
+ counts as "too much", what to omit. A vague prompt gives a different answer
182
+ every time; a specific one gives the product its consistency.
183
+ - **Images:** check type and size on the client (e.g. ≤ 5 MB, jpeg/png/webp),
184
+ downscale large photos in a canvas before upload, send as a base64 data URL.
185
+ - Reasoning models may take 10–60s. Show progress, and make the route's
186
+ timeout longer than the model's.
187
+
188
+ ## 5. Build order
189
+
190
+ 1. Skeleton and design tokens, so every later piece is styled correctly first time.
191
+ 2. The server route with the real integration, tested with `curl` before any UI.
192
+ 3. The core loop UI, wired to the real route.
193
+ 4. Every state: empty, loading, success, error, and invalid input.
194
+ 5. Polish: motion, responsive, copy, favicon, page title and metadata.
195
+
196
+ Use `batch_write` for the skeleton — one call, every file.
197
+
198
+ ## 6. Prove it works
199
+
200
+ - `npm run build` — it type-checks and lints; a build that fails is not done.
201
+ - Start it: `npm run dev` goes to the background on its own and comes back with
202
+ the URL once ready. Do not start it twice.
203
+ - Exercise it: `curl` the API route with real input, then `look_at_app` on every
204
+ page — it loads them in a real browser at phone and desktop width and reports
205
+ errors, overflow and a visual review. A clean build proves it compiles, not
206
+ that it works.
207
+ - Fix what you find and check again.
208
+
209
+ ## 7. Definition of done
210
+
211
+ - The core loop works end to end against the real service.
212
+ - No `TODO`, no placeholder copy, no dead buttons, no console errors.
213
+ - Every async action has loading, success and error states.
214
+ - Invalid input is caught with a useful message before it reaches the server.
215
+ - Secrets only on the server.
216
+ - `npm run build` passes.
217
+
218
+ ## 8. Report
219
+
220
+ What you built, the URL, how to start it again, and what you checked. If
221
+ anything is untested or unfinished, name it — one sentence of honesty saves the
222
+ user an hour of finding out on their own.