Readable source, contributor setup, demo, example links, tidy root - #6
Conversation
docs/kernel-agent.html and docs/kernel-agent-mobile.html are now built by scripts/build.mjs from src/. Each is still one self-contained file. - src/agent/desktop.html and mobile.html are the page templates. "<!-- @include path -->" lines pull in CSS, shared markup, and JavaScript. - The one large script is split along its existing section markers into 60 files under src/agent/js/{notebook,agent,app,mobile}/. They are concatenated in name order into the same single function scope as before. - The Python kernel harness is src/agent/python/harness.py, plain Python that the build escapes into the page. Its five \u escapes are now doubled so Python, rather than JavaScript, decodes them; the text is unchanged. - Mobile shares desktop's .prompt-acts wrap rule, so layout.css is one file. The split was verified mechanically: before the header line was added, the build reproduced both pages byte for byte. The only change to the built pages is that header, which says where to edit. build.mjs replaces sync_agent_builds.mjs: the parts both builds share now exist once in src/ instead of being copied from desktop into mobile. CI runs "node scripts/build.mjs --check". src/README.md maps the tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
The JavaScript in src/ was hand-compressed: 3,357 lines, many of them single-line functions over 1,000 characters. Prettier now formats it (11,700 lines, 120 columns). An acorn comparison of all 56 files before and after shows identical syntax trees, apart from regex flags written in a different order. The formatting changed layout, not behavior. Tests no longer depend on how the code is written: - tests/lib/source.mjs replaces three copies of brace-counting and regex extraction. functionSource() and declarations() find code by parsing it, and has() matches a snippet as text or as the same JavaScript tokens, so `model:'x'` still finds `model: "x"`. codeIndex() replaces indexOf() for ordering checks. - Assertions that read code with regexes now test behavior instead. For example, add_cells and edit_cell must accept run: true in the actual AG_TOOLS schema. - The E2E server finds the startup function with a whitespace-tolerant pattern. Contributor setup: - package.json holds the release version and the dev tools (prettier, acorn, playwright). npm run build, check, format, test, and test:e2e wrap the existing scripts. CI uses npm ci and npm run check, which also fails on unformatted source. - %VERSION% in src/ becomes package.json's version at build time, and code reads it as APP_VERSION. Titles, the brand, export manifests, and diagnostics no longer hardcode 2.4.0 in eight places. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
Modeled on procedural-art's setup: - CONTRIBUTING.md covers the layout, the commands to run before a PR, how to write tests that survive formatting, rules of thumb (one file and no server, keys stay in the browser, desktop and mobile share code, specs move with the agent), and how to release. - CHANGELOG.md covers every release so far, with this branch under Unreleased. - Issue templates for bugs and ideas. The bug template asks which app and points to the redacted Export diagnostics bundle. - The release workflow runs on a vX.Y.Z tag or by hand. It checks the tag against package.json, runs the checks, and publishes a GitHub Release. The notes come from the matching CHANGELOG section, and the single-file apps and a freshly zipped kernel-notebooks.skill are attached. - CI checks that kernel-notebooks.skill still matches skill/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
- docs/media/agent-demo.gif (1.2 MB, 23 s) shows KERNEL·A loading the sample dataset and answering "Which region and channel bring in the most revenue?". The agent writes and runs a pivot table and a stacked bar chart, then summarizes. scripts/record-demo.mjs (npm run demo) recreates it against a paced local mock of the Anthropic API, so no key is needed. The Python, tables, and chart are real, and the summary is computed from the data, so its numbers are true. Frames share one palette and store only changed pixels. - ?example=<name> opens a curated run from examples/ with its original outputs. Only folder names are accepted, the notebook comes from this repository, and nothing runs. A notebook with work in it is kept, and the example opens in a new one. The parameter is removed after use. The welcome card gains "See a real agent run". - The README opens with a centered pitch, the demo, CI and license badges, launch links, and a table of the four real runs with "Open in KERNEL·A" links. The duplicated examples section is merged into it. The landing page shows the demo under the hero and has a "Real agent runs" section. - The model chip in the agent composer no longer wraps onto two lines while a run is active. - E2E: the harness serves example fetches from the checkout, and the UI suite covers opening examples (new notebook when there is work, invalid and unknown names, ?example= in a fresh browser). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
canonicalResponseOutput turned unparseable function_call arguments into
{} without flagging them, so on OpenAI and xAI a tool with no required
fields ran anyway. A malformed run_all reran the whole notebook. The
Responses adapter now sets invalidInput, as the Anthropic adapter
already did, for arguments that don't parse or aren't a JSON object.
execTool then returns INVALID_JSON and executes nothing.
verify_agent_v24.mjs covers malformed, non-object, and valid arguments.
The new check fails on the previous build and passes on this one.
Reported by the Codex review on #5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
The root held four agent specs, a zipped skill, a formatter config and the contributing guide next to the README. Each now lives with the things it belongs to: - AGENT-SPEC.md, AGENT-V2/V23/V24-SPEC.md move to specs/ as agent-v1.md, agent-v2.md, agent-v2.3.md and agent-v2.4.md, with an index that says which is current and what each covers. - kernel-notebooks.skill moves to docs/, so GitHub Pages serves it at a stable download URL. CI checks it against skill/ at the new path. - CONTRIBUTING.md moves to .github/, where GitHub still surfaces it. - The Prettier settings move into package.json. - Every reference follows: specs and their cross-links, the landing page, the changelog, the source comment the built pages carry, and the tests that read the v2.3 spec. The README is shorter. The file tree, the duplicated introduction, and the release-check block (already in CONTRIBUTING) are gone, and the 24-point agent feature list is now ten points. A short "Use it" section and a folder table replace them. The root is now README, CHANGELOG, LICENSE, package files, and folders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8151391825
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
When the current notebook had work in it, openExample waited for busy to clear and then called newNotebook(). That call refuses while an agent run is active or a cell is still running, but it returned quietly, so the import went ahead: fromIpynb replaced the current notebook's cells and could send the running agent's next tool calls into the example. - newNotebook() now resolves to true when it switches and false when it refuses. Existing callers ignore the value. - openExample refuses up front while an agent run is active. After the download it checks again, because a run or cell may have started, and aborts if newNotebook() refuses. A blank notebook is also protected while a run is active. The toast says to finish or stop the run first. - ui.mjs covers three cases: a run active before opening, a run that starts during the download (delayed fetch), and newNotebook() reporting a refusal. The download case fails on the previous build and passes now. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2199641acd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The blank-notebook path of openExample checked only agRunning. A paused or failed run leaves agRunning false, so an example could load into the notebook and thread that own the run, and resuming the run would then execute its pending tools against the example. A kernel restart or a running cell (busy) was also not checked on that path. - openExample now waits for the kernel on every path (boot, restart, or a running cell), then refuses if a run is active or the kernel is still busy. - The current notebook is reused only when it is empty and its agent has no unfinished run (agRun not in RUN_TERMINAL). Otherwise the example opens in a new notebook, and the paused run stays with its own. - ui.mjs covers a kernel that stays busy and a blank notebook with a paused run. The paused-run case fails on the previous commit and passes now. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1be3132ce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
newNotebook() checked agRunning and busy only before its awaited saves, runtime reset, and agent activation. A run started or resumed during those awaits still let the switch finish, and openExample then imported the example while the run was active. - newNotebook() and switchNotebook() hold a notebookSwitching flag for the whole transition. agentTurn and resumeAgentRun refuse while it is set, with the message "Wait for the notebook switch to finish". A second switch requested during a transition is refused the same way. The bodies move unchanged into createAndOpenNotebook() and openExistingNotebook(). - openExample checks agRunning and busy again after newNotebook(), so a run or cell that got going during the switch anyway stops the import. The user is left in the new empty notebook. - ui.mjs covers agentTurn refusing mid-switch, and a run that starts during the switch's save stopping the import. The second case fails on the previous commit and passes now. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11a8ed708e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The switch lock stopped agent runs, but runCell() and restartKernel() did not check it. Shift+Enter, Run, or Restart during newNotebook() or switchNotebook() could claim the kernel after the switch's first check. The switch then reset Python for the next notebook, cutting the cell off or leaving its state in the wrong notebook. - runCell() refuses while notebookSwitching is set. It returns false with "Wait for the notebook switch to finish", which covers Run, Shift+Enter, Run all, and explore cells. Markdown cells still render. - restartKernel() refuses the same way. Interrupts are unaffected: they act only on a busy kernel, and nothing can make it busy mid-switch. - ui.mjs checks that a cell neither runs nor gets an execution count, and that the kernel generation doesn't change, while a switch is in progress. The run check fails on the previous commit and passes now. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 62a22346bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Two gaps from the Codex review: - Startup restores the open notebook's saved files only after Python boots, and boot never sets busy. An ?example link on a notebook with blank cells but saved files therefore saw an empty dataFiles, treated the notebook as blank, replaced it, and saveWorkspaceState wrote an empty file list over the saved workspace. bootKernel now records when the restore happened (startupRestored) and when boot finished either way (startupSettled). openExample waits for both, and refuses if boot failed rather than guess. - runCell awaited refreshReferencedArtifacts after its checks but before claiming busy, so a notebook switch could start and finish in between and the cell would then run detached. A cellStarting claim now covers that await, and newNotebook and switchNotebook refuse while it is held. runCell also checks again afterwards that no switch is in progress and the cell is still in the notebook. No other await sits between that check and busy = true. ui.mjs reproduces both: a saved notebook with a file reopened through an example link, and a switch started while a cell prepares its files. Both fail on the previous commit (the saved file was wiped; the cell ran after the switch) and pass now. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f7b13b3232
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
openExample didn't look at notebookSwitching. While switchNotebook() was still saving or restoring, the notebook being switched to could look blank because its files weren't back yet. The example then reused it and imported its cells while the switch carried on restoring that notebook's workspace and activating its thread. A notebookOccupied() helper (busy, a cell preparing, or a switch in progress) is now used by the wait and by both checks. openExample waits for a switch to finish before deciding whether the notebook is blank, and refuses if one is still running. ui.mjs covers opening an example during a switch: on the previous commit the half-switched blank notebook was replaced; now it is left alone. The test for a run starting during the example's own switch now puts a cell in the notebook first, so the example opens a new notebook whatever state earlier tests leave behind. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ca8c4c610d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A notebook whose cells were run and then deleted looks blank, but its Python namespace still held their variables and imports. An example opened into it inherited that state, so rerunning the example could read values it never defined. The reuse path now resets the runtime with isolateNotebookRuntime(), as a new notebook does. It holds the switch lock while Python resets, so no cell, run, restart or switch starts meanwhile, and it checks the notebook is still blank afterwards so work added during the reset is never replaced. ui.mjs covers both. On the previous commit a variable from a deleted cell survived into the example, and a cell typed during the reset was overwritten; now the namespace is clean and the typed cell is kept. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff2ad66372
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Every example opened with "Run all recomputes them", but the Fleet DNA run reads a public CSV and PDF that the repository deliberately doesn't include, so rerunning it fails on the first data cell. EXAMPLES_NEEDING_DATA lists the examples that read outside data. For those, the message says rerunning needs files that aren't included and offers a "Where to get them" button that opens the example's SOURCE.md. The other examples keep the original message. verify_examples.mjs checks that the list matches the examples that have a SOURCE.md, and ui.mjs checks the Fleet DNA message and its button. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
5fe5707 to
9895432
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9895432459
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
init starts activateNotebookAgent(nbId) without awaiting it. That call sets agTxNb before it reads the thread and loadRunForThread(), so bootKernel saw the notebook as activated, skipped its own awaited activation, and marked startup settled while agRun was still unset. A linked example could then treat a blank notebook with a paused run as free, replace its cells, and leave the run to resume against them. init now keeps that activation as startupAgent, and bootKernel awaits it before startup counts as settled. ui.mjs saves a paused run in a blank notebook, then reopens it through an example link while reads of saved runs are held until after Python is ready. On the previous commit the example replaced the notebook; now it opens in a new one. verify_agent_v23.mjs follows the new init line. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eced74292b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An example reused a notebook whose cells and files were blank even when its agent thread held an earlier conversation, a plan, or a finished run. The example then sat beside that chat, and the next request sent the old conversation as context. hasAgentHistory() replaces hasUnfinishedRun(). It counts messages, a plan, comparisons, any loaded run, or more than one thread, and canReuseNotebook() combines it with isBlankNotebook() for both the reuse decision and the recheck after the reset. A notebook with agent history is kept and the example opens in a new notebook and thread. ui.mjs gives a blank notebook one earlier message and opens an example. On the previous commit the example was imported beside that message; now it opens in a new notebook with an empty thread. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e736c0b27c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
newNotebook() renders the new blank notebook and then awaits its workspace restore and agent activation, and the editor stays usable meanwhile. The check that the target notebook was still blank only ran when an existing notebook was reused, so anything typed into a new one during those awaits was replaced by the example. canReuseNotebook() is now checked after both paths, reset or new. If work was added, the notebook is kept and a message says so, instead of the "finish or stop the current run" message, which didn't fit. ui.mjs types into the new notebook while its agent activates. On the previous commit the example replaced that cell; now the cell is kept and the message explains why. Reported by the Codex review on #6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm
This covers the two follow-ups planned after #5, one fix from its review, and a cleanup of the repository root.
Readable source
Built from modules.
docs/kernel-agent.htmlanddocs/kernel-agent-mobile.htmlare now built byscripts/build.mjsfrom about 60 files insrc/:harness.py.Each page is still one self-contained file, and
src/README.mdmaps the tree.Mechanical split. The first build reproduced both pages byte for byte.
build.mjsreplacessync_agent_builds.mjs.Prettier formatting. 3,357 dense lines became 11,700 at 120 columns. Comparing acorn syntax trees for all 56 files before and after found no difference except the order of regex flags.
Tests read code through a parser.
tests/lib/source.mjsreplaces three copies of regex and brace-counting extraction:functionSourceanddeclarationsfind code by parsing it.has()matches snippets as JavaScript tokens, so formatting can't break an assertion.codeIndex()handles checks that one piece of code comes before another.add_cellsandedit_cellmust acceptrun: truein the actualAG_TOOLSschema.One version number. It lives in
package.json.%VERSION%in the source andAPP_VERSIONin code replace eight hardcoded copies.Contributor setup
npm run build | check | format | test | test:e2e | demo.npm ci.npm run checkfails ifdocs/is out of date or the source isn't formatted, and a separate CI step checks thatdocs/kernel-notebooks.skillstill matchesskill/..github/CONTRIBUTING.md, modeled on procedural-art, covers the layout, the commands to run before a PR, how to write tests, rules of thumb, and releasing.vX.Y.Ztag or by hand. It checks the tag againstpackage.json, runs the checks, takes the notes from the changelog, and attaches the single-file apps and the skill.Demo and examples
docs/media/agent-demo.gif(23 s, 1.2 MB) is at the top of the README and the landing page. The agent answers a revenue question about the sample data by writing and running a pivot table and a chart.npm run demorecords it against a paced local mock of the Anthropic API, so no key is needed.kernel-agent.html?example=<name>opens one of the curated runs inexamples/with its original outputs.SOURCE.mdinstead of promising that Run all recomputes it.Tidy root
specs/asagent-v1.md,agent-v2.md,agent-v2.3.md, andagent-v2.4.md, with an index that says which is current.docs/, so the site serves it for download. CONTRIBUTING moved to.github/, and the Prettier settings moved intopackage.json. Git records all of these as renames.Fixes
run_allreran the whole notebook. It is now refused, as it already was on Anthropic. The new test fails on the previous build. Reported by the Codex review on KERNEL Agent v2.4: cheaper, interruptible agent runs and a UI/UX pass #5.Hardening from review
The Codex review on this PR found races around example links and notebook switches. Each one was reproduced on the previous build, and each fix has a browser test that fails without it.
newNotebook()andswitchNotebook()hold anotebookSwitchinglock for the whole transition.Verification
npm run checkandnpm testpass.verify_examples.mjsalso checks that the app's list of data-backed examples matches the examples with aSOURCE.md.npm run test:e2epasses on desktop and mobile. It covers the agent loop, the kernel worker, and the UI, including every case above.skill/.The example links, the skill download, and the new README work on the live site once this merges to
main.🤖 Generated with Claude Code
https://claude.ai/code/session_01S9PfSmbaV1CY9ZawtfbiEm