If a prototype does something useful but you cannot explain its code, make the next task small enough to understand: name one behavior, trace the code responsible for it, reproduce a failure and check a focused fix. You can make progress without first rewriting the whole application.

Here, a small reading-list search will make that process concrete. Simon Willison offers a way to understand unfamiliar generated code; Addy Osmani supplies a workflow for planning and checking the next change.

Find the part you need to understand

In his March 2025 explanation of vibe coding, Willison uses the term for building with an LLM without reviewing its code. He distinguishes that from development in which someone reads, tests and understands the result. The distinction gives you a useful next goal: understand the behavior you intend to change.

His Linear walkthroughs chapter describes what happened after he generated a SwiftUI presentation app. He had published the code but did not understand its implementation. He then asked a coding agent to explain it in sequence, extracting source snippets through commands rather than copying them into an explanation by hand. The resulting Present walkthrough connects six Swift files to the application's behavior.

For your own prototype, begin with a narrower walkthrough. If the problem is search, trace the text from the input field through normalization, matching and display. Ask for the actual file and function at each step, then read the cited code. An explanation helps you navigate; agreement with the implementation still needs checking.

For example, you could ask:

Explain how text entered in the reading-list search becomes
the list shown on screen. Show the relevant source snippets
and name their files and functions. Do not change the code yet.

If the explanation names a function you cannot find, resolve that mismatch before editing. You need a map of the code you have.

Give the next change a visible target

Osmani's LLM coding workflow starts with a specification and breaks implementation into small tasks. He combines relevant context with tests, review and frequent commits. For an existing prototype, you can apply that idea to one repair: write down what the user should see and which behavior must stay the same.

Suppose a reading list contains these three titles, in this order:

const titles = [
  "LLM evaluation basics",
  "A guide to local LLMs",
  "Agent security notes"
];

The requirement is: find a piece of text anywhere in a title (a substring), ignore letter case and surrounding query spaces, preserve title order, and show every title when the query is empty. A query with no match should return an empty list. All titles and queries in this example are strings; fuzzy matching and language-specific search rules are outside this repair.

These are the expected results, chosen before changing the function:

Query Expected titles, in order
llm LLM evaluation basics; A guide to local LLMs
LLM LLM evaluation basics; A guide to local LLMs
zzz None
Empty string All three

This is a teaching example, created for this article. It is not code from Willison's or Osmani's projects, and it is not a record of a model's output.

Reproduce the failure before asking for a fix

Consider this faulty implementation:

function filterTitles(titles, query) {
  const needle = query.trim().toLowerCase();
  return titles.filter(title => title.toLowerCase().indexOf(needle));
}

For llm, it returns A guide to local LLMs and Agent security notes. The first correct match disappears, and an unrelated title remains.

The reason is visible in the return values. indexOf() gives the position of a match, or -1 when there is none. The first title returns 0, which JavaScript treats as false in this filter. The second returns a positive number, treated as true. The unrelated title returns -1, also treated as true. The code asks a position to do the job of a yes-or-no answer.

That diagnosis is more useful than asking an agent to improve search. You can now request a contained repair:

Fix filterTitles so it satisfies the four expected cases above.
Preserve trimming, case handling and result order. Keep this
change limited to substring matching. Explain the changed line
and run the cases against the old and new versions.

The expected results remain the reference. A suggested fix has to earn acceptance by producing them.

Make the focused fix and check every case

Replace the function with this version:

function filterTitles(titles, query) {
  const needle = query.trim().toLowerCase();
  return titles.filter(title => title.toLowerCase().includes(needle));
}

includes() returns a boolean. It also returns true for an empty search string, which matches this example's deliberate empty-query rule. Lowercasing and trimming remain in place.

With titles and the function defined, this code checks the result arrays, including their order:

const cases = [
  ["llm", [titles[0], titles[1]]],
  [" LLM ", [titles[0], titles[1]]],
  ["zzz", []],
  ["", [titles[0], titles[1], titles[2]]]
];

for (const [query, expected] of cases) {
  const actual = filterTitles(titles, query);
  const pass = JSON.stringify(actual) === JSON.stringify(expected);
  console.log({ query, actual, pass });
}

Running these cases in Node.js 20.10.0 produced the following comparison:

Query Faulty function Revised function
llm Second and third titles First and second titles — pass
LLM Second and third titles First and second titles — pass
zzz All three titles Empty list — pass
Empty string Empty list All three titles — pass

The repair satisfies the four stated cases. It also has an explanation you can check against the code: the filter now receives a boolean indicating whether the normalized title contains the normalized query. The list's order is preserved.

Follow the result back to the screen

A passing function check establishes the matching behavior on those inputs. To check an actual app, continue along the path you mapped earlier. Confirm that the input field calls this function, that its result reaches the displayed list, and that clearing the field restores the list. Try the no-match query and inspect what the page tells the reader.

This matters because the screen may still use an older function, retain stale results or display the wrong count. Run the project's relevant existing checks as well. A focused change makes the evidence easier to inspect; its size alone does not establish that every surrounding behavior works.

Keep a short handoff with the change: the requirement, the failing input and output, the changed line, the checked results, and the behavior you have not checked. A diff—the before-and-after code comparison—lets another reader see whether the implementation stayed within that task. Once reviewed, a version-control checkpoint gives the next change a clear starting point.

For the next coding session, return to Willison's walkthrough work when understanding is the obstacle, or Osmani's workflow when the task keeps expanding. The engineering reading task connects both approaches to other authors' work.

If the application itself generates answers with an LLM, evaluating an AI application extends the exercise to response quality and repeated comparisons. If you are assessing someone else's product demonstration, the agent-demo field note explains what its visible actions and checked outcome can establish.

When a model choice rests on a paper’s reported score, reading an LLM research result helps you recover the comparison and its conditions. For other implementation and evaluation methods, continue through applied AI engineering.