{
    "archive_path": "archive/1783234114.999708",
    "base_url": "htmx.org/essays/working-with-ai",
    "basename": "",
    "bookmarked_date": "2026-07-05 06:48",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/htmx.org/essays/working-with-ai",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=htmx.org",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "htmx.org",
    "downloaded_at": "2026-07-05T06:48:37.821962+00:00",
    "downloaded_datestr": "2026-07-05 06:48",
    "extension": "",
    "hash": "MYEN55HX9KP8FDBQVYYR",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T06:49:35.216752+00:00",
                "index_texts": null,
                "output": "https://web.archive.org/web/20260705064919/https://htmx.org/essays/working-with-ai/",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:49:14.613180+00:00",
                "status": "succeeded"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2026-07-05T06:48:53.581608+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:48:42.277384+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=htmx.org"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T06:48:42.022246+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:48:37.964438+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T06:48:42.172094+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:48:42.063788+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2026-07-05T06:49:07.378375+00:00",
                "index_texts": [
                    "</> htmx ~ Working With AI: A Concrete Example (https://htmx.org/essays/working-with-ai/) (Sitewide Atom feed) (/atom.xml) (/css/site.css) (/favicon.svg)  (/) </ > htmx       (/docs/) docs  (/reference/) reference  (/examples/) examples  (/talk/) talk  (/essays/) essays  (+[inurl:https://htmx.org]) Search (\ud83d\udd0d\ufe0f)    (https://github.com/bigskysoftware/htmx) star        Working With AI: A Concrete Example Carson Gross June 29, 2026 I am, generally, ambivalent towards AI.  There is no doubt it has become a very powerful tool for development in the\nlast year, but it also comes with many dangers, both\nfor us individually (e.g. the slow dulling of our intellects) as well as collectively (e.g. environmental concerns,\nincreasingly expensive personal computing, etc.) In (https://htmx.org/essays/code-is-cheap/) \u201cCode is Cheap(er)\u201d , I warn about The Sorcerer\u2019s Apprentice problem, where a developer becomes reliant on AI and is unable to\nunderstand and properly address issues that come up in the systems they are building. In this article I want to go through a specific interaction that I had with AI while maintaining (https://hyperscript.org) hyperscript to show the strengths and weaknesses of AI in general and to demonstrate\nThe Sorcerer\u2019s Apprentice problem (which I narrowly avoided) in particular. The Hyperscript Parser  For some background, hyperscript is an alternative interpreted scripting language for the web.  It is, ironically,\nwritten (https://github.com/bigskysoftware/_hyperscript/blob/master/src/_hyperscript.js) entirely in JavaScript . It is a strange piece of software: I intentionally broke many of the rules of parsing when writing it as an experiment\nto see how things would work out. Some examples: Parsing logic is colocated (https://github.com/bigskysoftware/_hyperscript/blob/ea9a6534d24cf5c7257adcaad75ee75b0c612d8e/src/parsetree/expressions/expressions.js#L275) on parse elements  The parser is pluggable, and the grammar is (https://github.com/bigskysoftware/_hyperscript/blob/ea9a6534d24cf5c7257adcaad75ee75b0c612d8e/src/_hyperscript.js#L63) defined dynamically  It supports multiple syntaxes for (https://hyperscript.org/docs/language/#properties) property access .  It is not an approach I would recommend for most programming languages, but it has worked out pretty well for this\nproject. Yet another demonstration that there are indeed multiple ways to skin the cat in software. A Bug Report  Our story begins when a user reported a regression when upgrading to the 0.9.91 release.  The following\nexpression no longer parsed properly:  fetch `{% url ' trade :get_symbol_data' %}?symbol=${symbol}` as JSON     In particular, the as JSON was binding too tightly and trying to convert the string literal into JSON before it\nwas handed to fetch instead of doing what the user expected (and what it did previously) namely fetching the\ngiven url with the results treated as JSON. This sort of binding conflict is a classic problem in parsing. Because hyperscript is an (https://en.wikipedia.org/wiki/HyperTalk#Descendants_of_HyperTalk) xTalk style language and\ninherits many of the ambiguities of English, this problem is all the worse in it. Investigating The Cause  The first thing to do was to investigate why this regression occurred. This is an area where I am typically going to lean on AI to help. I use Claude, and it did an admirable job finding the root cause: in 0.9.91 I had been overly\naggressive in refactoring the (https://hyperscript.org/commands/go/) go command to reuse/share logic with the (https://hyperscript.org/commands/fetch/) fetch command. I had extracted a common method for both of these commands to use, parseURLOrExpression() , but, in doing so, I\naccidentally expanded the grammar after the fetch command to include the general expression , er, expression. The as keyword has a meaning in expressions: it is a (https://hyperscript.org/expressions/as/) conversion expression ,\nallowing you to convert between types:  set  x  to  \"42\"  as Int    But the as keyword is also a modifier of the fetch command, telling it how to convert the response: fetch https://hyperscript.org as  Text    (Perhaps this fact makes you throw up a little bit in your mouth.  Good.) The crux of the issue was that, inadvertently in the refactor, I had made the parser parse an expression after a fetch keyword\nwhich was now consuming the as keyword as an expression, rather than allowing it to be a modifier for fetch . With the help of Claude I was able to figure this out in a few minutes, much faster than if I had had to\nfigure it out on my own. Fixing The Issue  AI was very helpful in finding the cause of the problem. In fixing the problem, however, it was much weaker. I will admit here I was being lazy and asked AI for a solution, so complaining about those solutions feels a bit, well,\nlazy, but I still think the string of events is informative, so let\u2019s go through exactly what happened. Proposed Fix 1: A Hack  The first suggestion that was given was to parse what is called a \u201cstring-like\u201d leaf first,\nthen fall back to a full expression: return  this . parseElement ( \"stringLike\" ) ||  this . requireElement ( \"expression\" );    This fix would have solved the immediate problem presented by the user. However, it was very specific to the reported bug and wouldn\u2019t have fixed the general case, such as if someone uses a variable as the target of a fetch: fetch $url as JSON    I rejected this proposal because of this: too hacky and not general enough. (Note that the hyperscript parser has plenty of organically supplied hacks in it, so this may have been the pot calling the\nkettle black.) Proposed Fix 2: Better But Unnecessary Complexity  The second proposal was more interesting: add a noConversions flag on the parser, set it around the URL parse, and have AsExpression.parse bail when it is set: // AsExpression.parse()  if  ( parser . noConversions )  return ;    This will horrify many parser engineers because it makes the hyperscript parser (https://dl.acm.org/doi/epdf/10.1145/362686.362695) context-sensitive . Good. The hyperscript parser was already context-sensitive. In looking at this fix and thinking for a second, I realized that we already had the hacky context-sensitive\ninfrastructure we needed without introducing a new flag on the parser, but Claude had missed it. \u201cFollows\u201d In The Hyperscript Parser  In the hyperscript parser we have a notion of (https://github.com/bigskysoftware/_hyperscript/blob/ea9a6534d24cf5c7257adcaad75ee75b0c612d8e/src/core/tokenizer.js#L11) \u201cfollows\u201d , that is, tokens that are claimed by a \u201chigher up\u201d parse element as a follow token. The hyperscript parser is (a somewhat strange) recursive descent parser, and this allows a parse element (usually a command)\nto \u201cclaim\u201d a keyword, and expressions won\u2019t match against them during parsing. As an example, the (https://hyperscript.org/features/when/) when  feature uses (https://github.com/bigskysoftware/_hyperscript/blob/264eca41bd6cb640a52bf0a312fdf901dba74810/src/parsetree/features/when.js#L27) or as a separator rather\nthan as a logical connective in its declaration: < div  _ = \"when $x or $y changes put it into me\" ></ div >    (I can hear many parser engineers closing this window in anger.  Good.) It turns out that this feature could be used to achieve what we wanted: rather than adding a new flag to the parser\nwe could push as as a follow, then parse the expression, then pop it as a follow. This would prevent the AsExpression from parsing, while still allowing most general expressions such as variables to work. Proposed Fix 3: Close, But No Cigar  I pointed this out to Claude and, in a frisson of excitement, it told me that I was \u201cabsolutely right!\u201d and set about\nusing this technique to fix the bug. Claude added the correct code to the parseURLOrExpression() which fixed the issue generally without adding any additional\nparser infrastructure. Good to go. The Final, Semi-Organic Fix  However, as I was reviewing the change, I realized that the new fix was overly broad: both fetch and go shared\nthis method, but only fetch used as to signal a modifier. The existing fix prevented the perfectly valid use of as conversion expressions in go commands as well. So I implemented the final fix myself, in FetchCommand#parse() :  parser . pushFollow ( \"as\" );   try  {   var  url  =  parser . parseURLOrExpression ();  }  finally  {   parser . popFollow ();  }    if  ( parser . matchToken ( \"as\" )) {  ...    Here I narrowed the special case to only the fetch command, leaving go parsing unaffected. This ended up being my final answer to the bug. Tests  Along the way I had Claude generate some tests for the various cases. There is a good existing test suite for hyperscript, and Claude did a good job of creating small, focused tests that showed the\nproblem and that the fix was working properly. Another area AI appears to work well. The Moral of The Story  OK, so what is interesting about this fairly mundane bug fix story? I think it is interesting to see where AI did well, namely in investigation and test creation, and to contrast that\nwith where it didn\u2019t do so well: coming up with a clean solution. If I had not been familiar with the hyperscript parser and its infrastructure this fix could have easily led to technical\ndebt being accrued in the project: another hacky parsing corner case, another bit of state on the parser, etc. Technical debt, I assert without evidence1  , grows exponentially, and therefpre it is very\nimportant to minimize it in your projects. This story shows how having a human in the loop, working with an agent and with a good understanding of the underlying\ninfrastructure, can be much more effective in controlling complexity than an agent left to its own devices. Some people will look at the hyperscript code base and scoff at the notion that controlling complexity was ever\na consideration at all.  I am sympathetic to that view. However, in this example we can see in a concrete scenario how complexity was restrained, at least a bit, in fixing an\nadmittedly embarrassing bug, by a knowledgeable human working with an AI agent. This is a situation where, rather than being a sorcerer\u2019s apprentice and blindly accepting the solutions AI proposed,\nI was acting as a sorcerer (I hope that\u2019s not too arrogant to say!) demanding a correct solution that better fit the\nexisting codebase\u2019s architecture. I understood the problem and saw the correct solution and was able to work with AI to achieve it and then verify the\nsolution with the help of AI-generated tests. This is in contrast, I hope a good contrast, with some forms of vibe coding currently being pushed in which developers (or whatever) appear\nto pride themselves on not understanding what is actually going on. Aside: AI & The Older Developer  Another thing occurred to me as I was going back over this experience. I am an older developer, having turned 50 this year.  As developers get older the reality is that we tend to\n\u201close our fastball\u201d, at least to some extent. Practically, for me, this has meant two things: I am not able to remember as much as I used to I am not able to work as long of hours as I used to  It turns out that AI directly addresses both of these issues. With respect to memory, while I can\u2019t remember everything I used to be able to, I can understand things again very\nquickly with appropriate, er, prompting.  AI is very good at helping me with this, and it lets me switch between open\nsource projects and work projects much more efficiently than if I didn\u2019t have it. With respect to the long hours, AI is able to grind in a way that, even as a young developer, I would have\nhad a difficult time keeping up with.  This means, for example, I can have a much more extensive test suite for my projects\nthan I would have otherwise. Looking at the tests that Claude generated in this case, they are more extensive than what I probably could have mustered\nthe energy to do myself. So AI has addressed two fundamental (relative) weaknesses I have developed as an older developer. On the other hand, I am very worried that it is also enabling a more general regression in my overall intelligence.  This\nis something that occurs naturally as you age anyway.   AI reliance may accelerate this process however and I have to\nsay, looking back at this story, I\u2019m a bit ashamed of how long I leaned on Claude before just doing the right thing\ndarned myself. This is an area I am still trying to navigate myself. Conclusion  I wanted to write up this series of interactions because I thought it captured some of the good and some of the bad\nof AI assistance in coding.  It demonstrated the value of a reasonably competent developer in the loop working\nwith an AI agent, and also showed the danger of blindly accepting the first (or second) solution that an AI agent\nsuggests to a problem. I hope that it is useful to you as you develop your own thoughts and strategies around AI agents. \u2013 1 This was revealed to me in a dream.  </>   haiku javascript fatigue:longing for a hypertextalready in hand    (/docs/) docs  (/reference/) reference  (/examples/) examples  (/talk/) talk  (/essays/) essays  (https://twitter.com/htmx_org) @htmx_org    ()       "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:49:07.356866+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2026-07-05T06:49:14.579095+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:49:07.503469+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2026-07-05T06:49:07.324046+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:49:00.831020+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmptt7lal3p",
                    "https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-07-05T06:48:59.884688+00:00",
                "index_texts": [
                    "Carson Gross\n    June 29, 2026\n  \n\n  I am, generally, ambivalent towards AI.  There is no doubt it has become a very powerful tool for development in the\nlast year, but it also comes with many dangers, both\nfor us individually (e.g. the slow dulling of our intellects) as well as collectively (e.g. environmental concerns,\nincreasingly expensive personal computing, etc.)\nIn \u201cCode is Cheap(er)\u201d, I warn about The Sorcerer\u2019s Apprentice problem, where a developer becomes reliant on AI and is unable to\nunderstand and properly address issues that come up in the systems they are building.\nIn this article I want to go through a specific interaction that I had with AI while maintaining\nhyperscript to show the strengths and weaknesses of AI in general and to demonstrate\nThe Sorcerer\u2019s Apprentice problem (which I narrowly avoided) in particular.\nThe Hyperscript Parser\nFor some background, hyperscript is an alternative interpreted scripting language for the web.  It is, ironically,\nwritten entirely in JavaScript.\nIt is a strange piece of software: I intentionally broke many of the rules of parsing when writing it as an experiment\nto see how things would work out.\nSome examples:\n\nParsing logic is colocated on parse elements\nThe parser is pluggable, and the grammar is defined dynamically\nIt supports multiple syntaxes for property access.\n\nIt is not an approach I would recommend for most programming languages, but it has worked out pretty well for this\nproject.\nYet another demonstration that there are indeed multiple ways to skin the cat in software.\nA Bug Report\nOur story begins when a user reported a regression when upgrading to the 0.9.91 release.  The following\nexpression no longer parsed properly:\n\nfetch `{% url 'trade:get_symbol_data' %}?symbol=${symbol}` as JSON\n\n\nIn particular, the as JSON was binding too tightly and trying to convert the string literal into JSON before it\nwas handed to fetch instead of doing what the user expected (and what it did previously) namely fetching the\ngiven url with the results treated as JSON.\nThis sort of binding conflict is a classic problem in parsing.\nBecause hyperscript is an xTalk style language and\ninherits many of the ambiguities of English, this problem is all the worse in it.\nInvestigating The Cause\nThe first thing to do was to investigate why this regression occurred.\nThis is an area where I am typically going to lean on AI to help.\nI use Claude, and it did an admirable job finding the root cause: in 0.9.91 I had been overly\naggressive in refactoring the go command to reuse/share logic with the fetch command.\nI had extracted a common method for both of these commands to use, parseURLOrExpression(), but, in doing so, I\naccidentally expanded the grammar after the fetch command to include the general expression, er, expression.\nThe as keyword has a meaning in expressions: it is a conversion expression,\nallowing you to convert between types:\n  set x to \"42\" as Int\n\nBut the as keyword is also a modifier of the fetch command, telling it how to convert the response:\n  fetch https://hyperscript.org as Text\n\n(Perhaps this fact makes you throw up a little bit in your mouth.  Good.)\nThe crux of the issue was that, inadvertently in the refactor, I had made the parser parse an expression after a fetch keyword\nwhich was now consuming the as keyword as an expression, rather than allowing it to be a modifier for fetch.\nWith the help of Claude I was able to figure this out in a few minutes, much faster than if I had had to\nfigure it out on my own.\nFixing The Issue\nAI was very helpful in finding the cause of the problem.\nIn fixing the problem, however, it was much weaker.\nI will admit here I was being lazy and asked AI for a solution, so complaining about those solutions feels a bit, well,\nlazy, but I still think the string of events is informative, so let\u2019s go through exactly what happened.\nProposed Fix 1: A Hack\nThe first suggestion that was given was to parse what is called a \u201cstring-like\u201d leaf first,\nthen fall back to a full expression:\nreturn this.parseElement(\"stringLike\") || this.requireElement(\"expression\");\n\nThis fix would have solved the immediate problem presented by the user.\nHowever, it was very specific to the reported bug and wouldn\u2019t have fixed the general case, such as if someone uses a variable as the target of a fetch:\n  fetch $url as JSON\n\nI rejected this proposal because of this: too hacky and not general enough.\n(Note that the hyperscript parser has plenty of organically supplied hacks in it, so this may have been the pot calling the\nkettle black.)\nProposed Fix 2: Better But Unnecessary Complexity\nThe second proposal was more interesting: add a noConversions flag on the parser, set it around the URL parse, and have\nAsExpression.parse bail when it is set:\n// AsExpression.parse()\nif (parser.noConversions) return;\n\nThis will horrify many parser engineers because it makes the hyperscript parser\ncontext-sensitive.\nGood.\nThe hyperscript parser was already context-sensitive.\nIn looking at this fix and thinking for a second, I realized that we already had the hacky context-sensitive\ninfrastructure we needed without introducing a new flag on the parser, but Claude had missed it.\n\u201cFollows\u201d In The Hyperscript Parser\nIn the hyperscript parser we have a notion of \u201cfollows\u201d, that is, tokens that are claimed by a \u201chigher up\u201d parse element as a follow token.\nThe hyperscript parser is (a somewhat strange) recursive descent parser, and this allows a parse element (usually a command)\nto \u201cclaim\u201d a keyword, and expressions won\u2019t match against them during parsing.\nAs an example, the when feature uses or as a separator rather\nthan as a logical connective in its declaration:\n<div _=\"when $x or $y changes put it into me\"></div>\n\n(I can hear many parser engineers closing this window in anger.  Good.)\nIt turns out that this feature could be used to achieve what we wanted: rather than adding a new flag to the parser\nwe could push as as a follow, then parse the expression, then pop it as a follow.\nThis would prevent the AsExpression from parsing, while still allowing most general expressions such as variables to work.\nProposed Fix 3: Close, But No Cigar\nI pointed this out to Claude and, in a frisson of excitement, it told me that I was \u201cabsolutely right!\u201d and set about\nusing this technique to fix the bug.\nClaude added the correct code to the parseURLOrExpression() which fixed the issue generally without adding any additional\nparser infrastructure.\nGood to go.\nThe Final, Semi-Organic Fix\nHowever, as I was reviewing the change, I realized that the new fix was overly broad: both fetch and go shared\nthis method, but only fetch used as to signal a modifier.\nThe existing fix prevented the perfectly valid use of as conversion expressions in go commands as well.\nSo I implemented the final fix myself, in FetchCommand#parse():\n  parser.pushFollow(\"as\");\n  try {\n    var url = parser.parseURLOrExpression();\n  } finally {\n    parser.popFollow();\n  }\n  \n  if (parser.matchToken(\"as\")) {\n      ...\n\nHere I narrowed the special case to only the fetch command, leaving go parsing unaffected.\nThis ended up being my final answer to the bug.\nTests\nAlong the way I had Claude generate some tests for the various cases.\nThere is a good existing test suite for hyperscript, and Claude did a good job of creating small, focused tests that showed the\nproblem and that the fix was working properly.\nAnother area AI appears to work well.\nThe Moral of The Story\nOK, so what is interesting about this fairly mundane bug fix story?\nI think it is interesting to see where AI did well, namely in investigation and test creation, and to contrast that\nwith where it didn\u2019t do so well: coming up with a clean solution.\nIf I had not been familiar with the hyperscript parser and its infrastructure this fix could have easily led to technical\ndebt being accrued in the project: another hacky parsing corner case, another bit of state on the parser, etc.\nTechnical debt, I assert without evidence1, grows exponentially, and therefpre it is very\nimportant to minimize it in your projects.\nThis story shows how having a human in the loop, working with an agent and with a good understanding of the underlying\ninfrastructure, can be much more effective in controlling complexity than an agent left to its own devices.\nSome people will look at the hyperscript code base and scoff at the notion that controlling complexity was ever\na consideration at all.  I am sympathetic to that view.\nHowever, in this example we can see in a concrete scenario how complexity was restrained, at least a bit, in fixing an\nadmittedly embarrassing bug, by a knowledgeable human working with an AI agent.\nThis is a situation where, rather than being a sorcerer\u2019s apprentice and blindly accepting the solutions AI proposed,\nI was acting as a sorcerer (I hope that\u2019s not too arrogant to say!) demanding a correct solution that better fit the\nexisting codebase\u2019s architecture.\nI understood the problem and saw the correct solution and was able to work with AI to achieve it and then verify the\nsolution with the help of AI-generated tests.\nThis is in contrast, I hope a good contrast, with some forms of vibe coding currently being pushed in which developers (or whatever) appear\nto pride themselves on not understanding what is actually going on.\nAside: AI & The Older Developer\nAnother thing occurred to me as I was going back over this experience.\nI am an older developer, having turned 50 this year.  As developers get older the reality is that we tend to\n\u201close our fastball\u201d, at least to some extent.\nPractically, for me, this has meant two things:\n\nI am not able to remember as much as I used to\nI am not able to work as long of hours as I used to\n\nIt turns out that AI directly addresses both of these issues.\nWith respect to memory, while I can\u2019t remember everything I used to be able to, I can understand things again very\nquickly with appropriate, er, prompting.  AI is very good at helping me with this, and it lets me switch between open\nsource projects and work projects much more efficiently than if I didn\u2019t have it.\nWith respect to the long hours, AI is able to grind in a way that, even as a young developer, I would have\nhad a difficult time keeping up with.  This means, for example, I can have a much more extensive test suite for my projects\nthan I would have otherwise.\nLooking at the tests that Claude generated in this case, they are more extensive than what I probably could have mustered\nthe energy to do myself.\nSo AI has addressed two fundamental (relative) weaknesses I have developed as an older developer.\nOn the other hand, I am very worried that it is also enabling a more general regression in my overall intelligence.  This\nis something that occurs naturally as you age anyway.   AI reliance may accelerate this process however and I have to\nsay, looking back at this story, I\u2019m a bit ashamed of how long I leaned on Claude before just doing the right thing\ndarned myself.\nThis is an area I am still trying to navigate myself.\nConclusion\nI wanted to write up this series of interactions because I thought it captured some of the good and some of the bad\nof AI assistance in coding.  It demonstrated the value of a reasonably competent developer in the loop working\nwith an AI agent, and also showed the danger of blindly accepting the first (or second) solution that an AI agent\nsuggests to a problem.\nI hope that it is useful to you as you develop your own thoughts and strategies around AI agents.\n\u2013\n\n\n  \n    </>"
                ],
                "output": "readability/",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:48:53.704211+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://htmx.org/essays/working-with-ai/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2026-07-05T06:48:53.646330+00:00",
                "index_texts": null,
                "output": "</> htmx ~ Working With AI: A Concrete Example",
                "pwd": "/data/archive/1783234114.999708",
                "schema": "ArchiveResult",
                "start_ts": "2026-07-05T06:48:53.632452+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "https://web.archive.org/web/20260705064919/https://htmx.org/essays/working-with-ai/",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "</> htmx ~ Working With AI: A Concrete Example",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1783234114.999708",
    "newest_archive_date": "2026-07-05T06:49:14.613180+00:00",
    "num_failures": 0,
    "num_outputs": 9,
    "oldest_archive_date": "2026-07-05T06:48:37.964438+00:00",
    "path": "/essays/working-with-ai/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01KWRGJDEK01316150012Q7G19",
    "snapshot_id": "12a72134-68c2-4df5-80be-8c43c573c029",
    "sources": [
        "/data/sources/1783234113-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1783234114.999708",
    "title": "</> htmx ~ Working With AI: A Concrete Example",
    "url": "https://htmx.org/essays/working-with-ai/"
}