ucode-agent 1.48.0 → 1.49.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.
@@ -188,7 +188,11 @@ export function forgetReviews() {
188
188
  const PROBE_TEXT = 'ucode check';
189
189
 
190
190
  /** Did anything at all happen on the page? */
191
- const moved = (a, b) => a.nodes !== b.nodes || a.text !== b.text || a.stored !== b.stored;
191
+ // The markup itself, not its length: a timer going 25:00 -> 24:59, a counter
192
+ // going 0 -> 1 or a task ticked off by a class change keeps every length the
193
+ // same, and was reported as "NOTHING HAPPENS" — a false accusation the model
194
+ // then "fixed" by rewriting script that worked.
195
+ const moved = (a, b) => a.nodes !== b.nodes || a.html !== b.html || a.stored !== b.stored;
192
196
 
193
197
  /**
194
198
  * Use the app, rather than only looking at it.
@@ -218,6 +222,7 @@ async function useTheApp(page) {
218
222
  const snapshot = () => page.evaluate(() => ({
219
223
  nodes: document.body.querySelectorAll('*').length,
220
224
  text: document.body.innerText.replace(/\s+/g, ' ').trim().length,
225
+ html: document.body.innerHTML,
221
226
  stored: (() => { try { return JSON.stringify(localStorage).length; } catch { return 0; } })(),
222
227
  }));
223
228
 
@@ -326,7 +331,7 @@ export async function withStaticServer(dir, fn) {
326
331
  }
327
332
  }
328
333
 
329
- export async function lookAtApp({ url, paths = ['/'] }) {
334
+ export async function lookAtApp({ url, paths = ['/'], review: withReview = true }) {
330
335
  const base = String(url ?? '').trim().replace(/\/+$/, '');
331
336
  if (!LOCAL.test(`${base}/`)) {
332
337
  throw new ToolFailure({
@@ -355,6 +360,10 @@ export async function lookAtApp({ url, paths = ['/'] }) {
355
360
  const errors = [];
356
361
  const failed = [];
357
362
  page.on('console', (m) => {
363
+ // The browser asks for /favicon.ico on its own. A page without one is
364
+ // not broken, and counting that 404 sent every plain-html build round
365
+ // a fix loop it did not need.
366
+ if (/\/favicon\.ico$/i.test(m.location()?.url ?? '')) return;
358
367
  if (m.type() === 'error' && !/devtools|download the react/i.test(m.text())) errors.push(m.text().slice(0, 200));
359
368
  });
360
369
  page.on('pageerror', (e) => errors.push(`uncaught: ${String(e.message).slice(0, 200)}`));
@@ -513,7 +522,7 @@ export async function lookAtApp({ url, paths = ['/'] }) {
513
522
  // error overlay is a minute spent on nothing.
514
523
  let critique = '';
515
524
  const state = reviews.get(base) ?? { done: false, tries: 0 };
516
- if (!broken && !state.done && state.tries < 2 && toReview.length) {
525
+ if (withReview && !broken && !state.done && state.tries < 2 && toReview.length) {
517
526
  state.tries++;
518
527
  reviews.set(base, state);
519
528
  try {
@@ -530,7 +539,7 @@ export async function lookAtApp({ url, paths = ['/'] }) {
530
539
  critique ? `## Visual review\n${critique}` : '',
531
540
  '',
532
541
  problems
533
- ? 'Fix the problems above, then look again to confirm.'
542
+ ? 'Fix the problems above - only those; leave everything that works as it is.'
534
543
  : state.done && critique
535
544
  ? 'The automatic checks found nothing. Weigh the visual review, fix what is worth fixing - ' +
536
545
  'the next look re-runs only the fast checks.'
@@ -12,7 +12,7 @@ import { addBlock, BLOCK_NAMES, PLAIN_BLOCK_NAMES, ALL_BLOCK_NAMES } from './blo
12
12
  import { typeOf } from './types.js';
13
13
  import { runCommand, runCommands } from './shell.js';
14
14
  import { webSearch } from './web.js';
15
- import { createApp, TEMPLATE_NAMES, TEMPLATE_NOTES } from './scaffold.js';
15
+ import { createApp, normaliseFiles, 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
 
@@ -702,13 +702,15 @@ export function describe(name, args = {}) {
702
702
  return `Running ${args.commands?.length ?? 0} commands together`;
703
703
  case 'deploy':
704
704
  return `Deploying ${clip(args.folder || '.', 30)} to Vercel`;
705
- case 'create_app':
705
+ case 'create_app': {
706
+ const files = normaliseFiles(args.files).length;
706
707
  // Say which starter it actually is. Hardcoding one of them meant a plain
707
708
  // HTML app announced itself as Next.js, which is a line that is simply
708
709
  // untrue on screen while the opposite happens on disk.
709
710
  return `Creating ${clip(args.name || args.folder, 30)} from the ` +
710
711
  `${args.template === 'next-shadcn' ? 'Next.js' : 'HTML'} starter` +
711
- `${args.files?.length ? ` with ${args.files.length} file${args.files.length === 1 ? '' : 's'}` : ''}`;
712
+ `${files ? ` with ${files} file${files === 1 ? '' : 's'}` : ''}`;
713
+ }
712
714
  case 'look_at_app':
713
715
  return `Looking at ${clip(args.url, 40)} on a phone and a desktop`;
714
716
  case 'web_search':
@@ -48,7 +48,7 @@ const SHOW_BACK = { 'plain-html': ['index.html', 'styles.css', 'app.js'] };
48
48
  * something adjacent. Each of those, refused, is a round trip spent being
49
49
  * told what could have been read — so they are all read.
50
50
  */
51
- function normaliseFiles(files) {
51
+ export function normaliseFiles(files) {
52
52
  let value = files;
53
53
  if (typeof value === 'string') {
54
54
  try { value = JSON.parse(value); } catch { return []; }