ucode-agent 1.43.0 → 1.45.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 +1 -1
- package/package.json +2 -1
- package/src/core/csscheck.js +77 -0
- package/src/core/jslogic.js +69 -0
- package/src/core/loop.js +71 -7
- package/src/tools/browser.js +546 -496
- package/src/tools/index.js +14 -7
- package/src/tools/scaffold.js +81 -3
- package/src/tools/shared.js +389 -343
- package/src/ui/plain.js +353 -353
- package/src/ui/screen.js +1699 -1692
- package/src/ui/theme.js +814 -776
package/src/tools/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createApp, TEMPLATE_NAMES, TEMPLATE_NOTES } from './scaffold.js';
|
|
|
16
16
|
import { deploy } from './deploy.js';
|
|
17
17
|
import { clip, READ_LINES } from './shared.js';
|
|
18
18
|
|
|
19
|
-
export { setRoot, setConfirm, getRoot } from './shared.js';
|
|
19
|
+
export { setRoot, setConfirm, setRequest, getRoot } from './shared.js';
|
|
20
20
|
|
|
21
21
|
const str = (description) => ({ type: 'string', description });
|
|
22
22
|
const int = (description) => ({ type: 'integer', description });
|
|
@@ -85,17 +85,21 @@ export const tools = [
|
|
|
85
85
|
'Start a new app AND write it, in one call. Pass "files" with the whole app and this ' +
|
|
86
86
|
'is the only call the build needs: the starter lands, your files are written over it, ' +
|
|
87
87
|
'and the result comes back with everything. Two starters. "plain-html" (the default): ' +
|
|
88
|
-
'one index.html, one stylesheet, one ES module — nothing to install, nothing to build, ' +
|
|
89
|
-
'and the stylesheet is a design system already: a palette, a spacing scale, radii, ' +
|
|
90
|
-
'motion timings, focus rings and a breakpoint. Re-tint those tokens to suit the app ' +
|
|
91
|
-
'and compose every rule from them. Replacing it with raw pixel values is what an ' +
|
|
88
|
+
'one index.html, one stylesheet, one ES module — nothing to install, nothing to build, ' +
|
|
89
|
+
'and the stylesheet is a design system already: a palette, a spacing scale, radii, ' +
|
|
90
|
+
'motion timings, focus rings and a breakpoint. Re-tint those tokens to suit the app ' +
|
|
91
|
+
'and compose every rule from them. Replacing it with raw pixel values is what an ' +
|
|
92
92
|
'undesigned page is made of. ' +
|
|
93
93
|
'opens straight in a browser, and its three files come back inside this result so there ' +
|
|
94
94
|
'is never a reason to read them. Use it for anything that is one page: a tasks app, a ' +
|
|
95
95
|
'toy, a game, a visualisation, a calculator, a timer. "next-shadcn": Next.js 16, ' +
|
|
96
96
|
'TypeScript, Tailwind 4 and shadcn with 33 components — only when the app genuinely ' +
|
|
97
97
|
'needs routes, a database or many screens, because it costs an install and a build. ' +
|
|
98
|
-
'This is how every Next.js app begins - never run create-next-app or shadcn init.'
|
|
98
|
+
'This is how every Next.js app begins - never run create-next-app or shadcn init. ' +
|
|
99
|
+
'In "next-shadcn" every path in "files" goes under src/: a page is a route only at ' +
|
|
100
|
+
'<app>/src/app/page.tsx or <app>/src/app/<segment>/page.tsx, an API handler only at ' +
|
|
101
|
+
'<app>/src/app/api/<name>/route.ts, and components at <app>/src/components/<feature>/. ' +
|
|
102
|
+
'A page.tsx written anywhere else is an ordinary file the router never serves.',
|
|
99
103
|
parameters: {
|
|
100
104
|
type: 'object',
|
|
101
105
|
properties: {
|
|
@@ -120,7 +124,10 @@ export const tools = [
|
|
|
120
124
|
},
|
|
121
125
|
template: {
|
|
122
126
|
type: 'string',
|
|
123
|
-
|
|
127
|
+
// The default goes first: a model picking from an enum reaches for
|
|
128
|
+
// the head of the list, and the head of this one used to be the
|
|
129
|
+
// starter that costs an install and a build.
|
|
130
|
+
enum: ['plain-html', 'next-shadcn'],
|
|
124
131
|
description:
|
|
125
132
|
'Which starter. "plain-html" (the default) for one page, a toy, a game, or any ' +
|
|
126
133
|
'app that does not need a server: no install, no build, nothing to wait for. ' +
|
package/src/tools/scaffold.js
CHANGED
|
@@ -14,7 +14,7 @@ import { promises as fs } from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { ToolFailure } from '../core/failure.js';
|
|
17
|
-
import { resolveIn, guard, result } from './shared.js';
|
|
17
|
+
import { resolveIn, guard, result, askedForPlainHtml } from './shared.js';
|
|
18
18
|
import { batchWrite } from './files.js';
|
|
19
19
|
import { packageJsonWritten, installIn } from './shell.js';
|
|
20
20
|
import { restore, populate } from './cache.js';
|
|
@@ -173,6 +173,60 @@ async function unprefixOwnFolder(appDir, written) {
|
|
|
173
173
|
return fixed;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Files that legitimately sit at the root of a Next.js project.
|
|
178
|
+
*
|
|
179
|
+
* Everything else a model writes there is app code that has missed src/.
|
|
180
|
+
*/
|
|
181
|
+
const NEXT_ROOT = /^(?:package(?:-lock)?\.json|next\.config\.[mc]?[jt]s|tsconfig\.json|postcss\.config\.[mc]?js|eslint\.config\.[mc]?js|components\.json|README\.md|TEMPLATE\.md|\.gitignore|next-env\.d\.ts)$/i;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Put Next.js files where Next.js looks for them.
|
|
185
|
+
*
|
|
186
|
+
* Routing is folder-based and unforgiving: a page is a route only at
|
|
187
|
+
* src/app/<segment>/page.tsx. A traced build wrote page.tsx at the project
|
|
188
|
+
* root and api/expenses/route.ts beside it — the build passed, every route
|
|
189
|
+
* 404ed, and nothing said why.
|
|
190
|
+
*
|
|
191
|
+
* The guide has the tree in it, and the guide cannot help: it comes back
|
|
192
|
+
* inside the create_app result, and the model chooses these paths in the call
|
|
193
|
+
* that produces that result. It is reading the map after it has parked. So the
|
|
194
|
+
* paths are corrected on the way in, and the result says what moved and why,
|
|
195
|
+
* which is the copy that arrives in time to matter for the next file.
|
|
196
|
+
*
|
|
197
|
+
* Only for next-shadcn, only for files under the app folder, and never for the
|
|
198
|
+
* config files that genuinely belong at the root.
|
|
199
|
+
*/
|
|
200
|
+
function placeForNext(files, folder) {
|
|
201
|
+
const moved = [];
|
|
202
|
+
|
|
203
|
+
const out = files.map((f) => {
|
|
204
|
+
const rel = String(f.path ?? '').split('\\').join('/');
|
|
205
|
+
const prefix = `${folder}/`;
|
|
206
|
+
if (!rel.startsWith(prefix)) return f;
|
|
207
|
+
|
|
208
|
+
const inside = rel.slice(prefix.length);
|
|
209
|
+
if (!inside || inside.startsWith('src/') || inside.startsWith('public/')) return f;
|
|
210
|
+
if (NEXT_ROOT.test(inside)) return f;
|
|
211
|
+
|
|
212
|
+
// app/... is the right tree written one level too high; everything else
|
|
213
|
+
// that is app code belongs under src/ as it stands.
|
|
214
|
+
const under = inside.startsWith('app/') ? `src/${inside}` : `src/app/${inside}`;
|
|
215
|
+
const isRoute = /(?:^|\/)(?:page|layout|loading|error|not-found)\.[jt]sx?$/.test(inside)
|
|
216
|
+
|| /(?:^|\/)route\.[jt]s$/.test(inside);
|
|
217
|
+
const isCode = /^(?:components|lib|hooks|styles|utils)\//.test(inside);
|
|
218
|
+
|
|
219
|
+
if (!isRoute && !isCode) return f;
|
|
220
|
+
const to = isCode ? `${prefix}src/${inside}` : `${prefix}${under}`;
|
|
221
|
+
if (to === rel) return f;
|
|
222
|
+
|
|
223
|
+
moved.push(`${inside} -> ${to.slice(prefix.length)}`);
|
|
224
|
+
return { ...f, path: to };
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return { files: out, moved };
|
|
228
|
+
}
|
|
229
|
+
|
|
176
230
|
async function keepDesignTokens(appDir, template, written, starterCss) {
|
|
177
231
|
const rel = TOKEN_FILE[template];
|
|
178
232
|
if (!rel || !starterCss) return null;
|
|
@@ -286,6 +340,16 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
286
340
|
});
|
|
287
341
|
}
|
|
288
342
|
|
|
343
|
+
// The user said no framework. A starter that costs an npm install and a
|
|
344
|
+
// build is then not a judgement call the model gets to make on their behalf
|
|
345
|
+
// — it is the one thing they ruled out, and the cost of getting it wrong is
|
|
346
|
+
// minutes of their time plus a rebuild from nothing.
|
|
347
|
+
let overrode = false;
|
|
348
|
+
if (template === 'next-shadcn' && askedForPlainHtml()) {
|
|
349
|
+
template = 'plain-html';
|
|
350
|
+
overrode = true;
|
|
351
|
+
}
|
|
352
|
+
|
|
289
353
|
const target = resolveIn(folder, 'create_app', 'folder');
|
|
290
354
|
const attempted = `creating an app in ${target.show}`;
|
|
291
355
|
if (target.show === '.') {
|
|
@@ -366,7 +430,9 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
366
430
|
|
|
367
431
|
// The app's own files, written in this same call. Two round trips become
|
|
368
432
|
// one, and round trips are nearly all of the time a build takes.
|
|
369
|
-
|
|
433
|
+
// Next.js only: paths that have missed src/ are corrected before they land.
|
|
434
|
+
const placed = template === 'next-shadcn' ? placeForNext(given, path.basename(target.abs)) : { files: given, moved: [] };
|
|
435
|
+
const mine = placed.files;
|
|
370
436
|
const wrote = mine.length ? await batchWrite({ files: mine }) : null;
|
|
371
437
|
const written = new Set(mine.map((f) => resolveIn(f.path, 'create_app', 'files').abs));
|
|
372
438
|
const keptTokens = await keepDesignTokens(target.abs, template, written, starterCss);
|
|
@@ -384,9 +450,21 @@ export async function createApp({ folder, name, description, template = 'plain-h
|
|
|
384
450
|
}
|
|
385
451
|
|
|
386
452
|
const out = result(
|
|
387
|
-
|
|
453
|
+
(overrode
|
|
454
|
+
? 'You asked for next-shadcn, but the request said plain HTML and no framework, so ' +
|
|
455
|
+
'this is the plain-html starter instead: three files, no install, no build. Write ' +
|
|
456
|
+
'the app in them.\n'
|
|
457
|
+
: '') +
|
|
458
|
+
`Created ${target.show} from the ${template} starter — ${copied.length} files, already known to build.\n` +
|
|
388
459
|
(look ? `Design: the ${look.name} preset (${look.summary}), font ${look.fonts?.sans ?? 'Geist'}.\n` : '') +
|
|
389
460
|
(wrote ? `\nYour ${mine.length} file${mine.length === 1 ? '' : 's'}:\n${wrote.content}\n` : '') +
|
|
461
|
+
(placed.moved.length
|
|
462
|
+
? `\nMoved into place: ${placed.moved.join(', ')}. In Next.js a page is only a route at `
|
|
463
|
+
+ 'src/app/<segment>/page.tsx, an API handler only at src/app/api/<name>/route.ts, and '
|
|
464
|
+
+ 'components and libraries live under src/. A file written outside src/ builds fine and '
|
|
465
|
+
+ 'is never served, which is the hardest kind of wrong to notice. Write the rest there '
|
|
466
|
+
+ 'directly.\n'
|
|
467
|
+
: '') +
|
|
390
468
|
(relinked.length
|
|
391
469
|
? `\nFixed in ${relinked.join(', ')}: links that began with "${path.basename(target.abs)}/". ` +
|
|
392
470
|
'Paths in "files" are relative to the project root, but a link inside a page is ' +
|