{"slug": "letterpaths-or-how-llms-can-be-good-even-when-they-re-bad", "title": "Letterpaths: Or how LLMs can be good even when they're bad", "summary": "Robin Linacre built Letterpaths, a library for generating cursive handwriting to help UK schoolchildren, in about 20 hours by using LLMs to create interactive GUIs that accelerated manual work, despite frontier models being unable to directly generate accurate letter shapes or joins. Linacre leveraged LLMs to build a letter editor, a join algorithm visualizer, and a kerning editor, enabling rapid human-in-the-loop iteration on the geometry and font output.", "body_md": "[letterpaths: or how LLMs can be good even when they’re bad](#letterpaths-or-how-llms-can-be-good-even-when-theyre-bad)\n\n[precise drawing](https://simonwillison.net/tags/pelican-riding-a-bicycle/). So how can we use them to build a library whose output is entirely visual?\n\n[Letterpaths](https://www.robinlinacre.com/letterpaths/) is a library for generating cursive handwriting to help schoolchildren in the UK. Its value rests on the quality of the letters and the joins between them.\n\nDevelopment was LLM-driven. But frontier models 1 were incapable of even basic visual tasks such as generating accurate letter shapes, let alone generating natural joins. Letters and joins therefore had to be built by hand.\n\nAnd yet, with the help of LLMs I was able to build the whole library in perhaps 20 hours of work.\n\nIn situations like this where the agentic loop fails, the goal is to put a human in the loop in the lowest-friction way possible - often by getting the model to build a GUI. Thus, despite being ill suited to the main task, AI was able to dramatically accelerate human work.\n\nThis post reviews some of the tricks I used to get the library working.\n\n[Building a letter editor](#building-a-letter-editor)\n\nI started by defining a [JSON schema](https://github.com/RobinL/letterpaths/blob/5a9645ac23c3778eaf8860f41cfaa75aa937d37d/packages/letterpaths/src/data/schemas/letter-formation-bezier.schema.json#L1) to represent the geometry of each letter.\n\nFrontier LLMs proved unable to convert an image of a letter into the schema. So I asked the LLM to build me the [letter editor](https://www.robinlinacre.com/letterpaths/editor.html). I also created a simple [letter gallery](https://www.robinlinacre.com/letterpaths/gallery.html) so I could easily see progress.\n\nOne useful trick here was to allow the browser app to save the JSON files straight back to disk for me using the browser’s File System Access API.\n\n[Building a join algorithm](#building-a-join-algorithm)\n\nThe join algorithm is the most important feature of the library. The joins need to look natural and closely approximate how joins are taught in school.\n\nThis turned out to be difficult to describe in words, and models proved incapable of improving the algorithm by looking at screenshots of the results and iterating.\n\nInstead, I asked the model to generate various different parameterised join algorithms and an interactive visualisation of the results that allowed me to tweak the parameters.\n\nAfter many iterations, the final algorithm accounted for horizontal and vertical distance, maximum curvature and total curvature, and also chose kerning (spacing) on their basis.\n\nBut it was still not quite right: there were too many edge cases; specific letter combinations that looked wrong.\n\nIn the end I built a [kerning and join editor](https://www.robinlinacre.com/letterpaths/kerning_editor/) that allowed me to review and edit and save all pairwise combinations, sorted in order of how common they were in the English language.\n\nMany could be saved using their default algorithmic settings, but this allowed me to target any pairs that looked wrong rapidly.\n\n[Building a font (.woff2, .otf)](#building-a-font-woff2-otf)\n\nLetterpaths contains scripts to output the letter and join geometry as a font.\n\nThis was particularly difficult to get right - and is still not perfect. But again, what helped was getting the LLM to create a visual interface to compare different algorithmic approaches:\n\nThe [font builder](https://github.com/RobinL/letterpaths/tree/main/packages/letterpaths-font) was the last part that I added to letterpaths, at which point GPT-5.5 and Opus 4.8 were available. Interestingly, only Opus 4.8 was able to do a passable job of generating the font without obvious discontinuities between letters. Even then, it had to be given guidance about how the algorithm should work.\n\n[Live reloading the GUIs when code was changed](#live-reloading-the-guis-when-code-was-changed)\n\nIt took me a while to figure out a good pattern for developing the core JavaScript library and associated GUIs. I wanted to ensure the main library code was separate from the disposable GUIs and other code. But at the same time, I needed updates to the core `letterpaths`\n\nlibrary to be immediately reflected in the GUI (live reload). This meant, for instance, that an update to the geometry of a letter would be immediately shown everywhere on save.\n\nThe [letterpaths repo](https://github.com/robinl/letterpaths) is an example of a pattern that worked well. It’s set up as a small `pnpm`\n\nmonorepo with a `packages/`\n\ndirectory containing the various apps. This allows the GUIs to use the `letterpaths`\n\nlibrary directly. The apps are run with `pnpm dev`\n\n.\n\nA secondary trick was to ensure the state of the GUI (which letter is loaded, any settings, which folder from the machine is open) was URL-encoded, so that live reload did not reset the state of the GUI.\n\n[Final thoughts](#final-thoughts)\n\nLLMs work best when a feedback loop can be established. Disposable GUIs can often help with this loop.\n\nThis feels like quite a general pattern whether you’re building an education app, a data pipeline, or really any other software: what is the thing you’re currently trying to optimise, and how can you visualise it with high information density. Even when the LLM may get there eventually, you’ll often be able to steer it faster if you can more precisely diagnose the current problem.\n\n[Footnotes](#footnote-label)\n\n-\nGPT 5.4 and Opus 4.6 at the time the library was written.\n\n[↩](#user-content-fnref-1)", "url": "https://wpnews.pro/news/letterpaths-or-how-llms-can-be-good-even-when-they-re-bad", "canonical_source": "https://www.robinlinacre.com/letterpaths_llms_good_even_when_bad/", "published_at": "2026-07-24 00:58:35+00:00", "updated_at": "2026-07-24 01:23:03.577157+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "developer-tools"], "entities": ["Robin Linacre", "Letterpaths", "GPT-5.5", "Opus 4.8"], "alternates": {"html": "https://wpnews.pro/news/letterpaths-or-how-llms-can-be-good-even-when-they-re-bad", "markdown": "https://wpnews.pro/news/letterpaths-or-how-llms-can-be-good-even-when-they-re-bad.md", "text": "https://wpnews.pro/news/letterpaths-or-how-llms-can-be-good-even-when-they-re-bad.txt", "jsonld": "https://wpnews.pro/news/letterpaths-or-how-llms-can-be-good-even-when-they-re-bad.jsonld"}}