14 min
technical

I Walked Away While Claude Edited My Blog. Then I Asked It to Show Me Everything.

Claude in Chrome handled 3 Substack tasks while I wasn't watching. A manual edit, a full migration, and a Future Prisca Note. Here's exactly how it did each one.

ClaudeAutomationAI DevelopmentBuilding in PublicSubagentsTeaching

I Walked Away While Claude Edited My Blog. Then I Asked It to Show Me Everything.

Published: March 5, 2026 - 14 min read

Earlier today, I gave Claude in Chrome three tasks on Substack. One was a manual edit to an existing post. One was a full blog post migration. One was inserting a Future Prisca Note into an older post. I pasted the migration prompts, and then I walked away.

I did not hover over the screen. I did not watch Claude click through menus or scroll through editors. I went and did other things while it worked. When I came back, everything was done. All three tasks. Published. Live. Correct.

And then I thought: I have no idea what actually happened.

So I asked Claude in Chrome to document its own workflow. Every action, every decision, every failure and recovery. What you are reading now is the result of that request, translated into something a human being can actually follow.

Why This Post Exists

This is the post I have been promising. In the blog post where I asked Claude in Chrome to explain itself, I covered the general mechanics: what tools it has, how it sees through screenshots, how it acts through JavaScript, and how the thinking loop ties everything together. But I deliberately left out the task-by-task breakdowns. I said those were coming.

In the blog post where I wrote about Igor Jarvis having two lives, I promised to walk you through exactly how Claude in Chrome handles the three specific migration scenarios that Igor is set up to manage: manual edits, new blog posts, and Future Prisca Notes.

This is me delivering on both promises. Same day, actually. (I told you the momentum does not stop.)

The Three Tasks

Here is what Claude in Chrome handled while I was not watching:

  1. Task 1: Manual Edit. Insert two new paragraphs with 5 hyperlinks and 2 italic words into an already published Substack post, "My People, I Owe You an Explanation," without re-sending the post to email subscribers.

  2. Task 2: Full Blog Post Migration. Take the blog post where Allen Kendrick reintroduced himself from my portfolio website, convert all the links, preserve every piece of formatting, insert subscribe buttons, and publish it as a brand new Substack post with email delivery turned on.

  3. Task 3: Future Prisca Note. Insert a Future Prisca Note into an older Substack post, "Claude God Tip #11: Meet Allen Kendrick," linking readers to Allen's new reintroduction, without triggering an email to subscribers.

Each task is different. Each has its own challenges. But the same three principles run through all of them: screenshots are how Claude sees. JavaScript is how Claude acts. And ProseMirror's document tree is what makes every insertion accurate down to the exact character position.

Let me show you what I mean.


Task 1: The Manual Edit

The Goal

My post "My People, I Owe You an Explanation" was already live on Substack. I needed two new paragraphs added between specific existing paragraphs, complete with 5 hyperlinks and 2 italic words. And critically, updating a published post should never re-send it to subscribers. That would be confusing and spammy.

Getting Into the Editor

Claude started by navigating to the live post on Substack. It took a screenshot to confirm the page loaded, then looked for an edit button. Here is something interesting: there is no visible edit button on a published Substack post. It is hidden behind a three-dot menu. Claude tried to find the button through the page's accessibility tree first, found nothing, and then used coordinates from the screenshot to click the three-dot menu instead.

This is the thinking loop I described in the previous post: look, try the smart approach, and when that does not work, fall back to the visual approach. Once the dropdown appeared, Claude clicked "Edit" and confirmed the editor opened by checking that the tab title changed to "Editing..." and the Tiptap toolbar was visible.

Mapping the Document

Before touching anything, Claude mapped the entire document. It used a tool called read_page to get a full picture of the editor's internal structure, which revealed each paragraph as a numbered reference. It found the exact paragraph mentioning the "777-1 experiment" (that is where the new content needed to go after) and confirmed what came next: "I want to change that." Now Claude knew precisely where to insert.

The Failure That Made Everything Better

Here is where it gets interesting. Claude's first attempt failed.

It tried the simpler approach first: a JavaScript command called document.execCommand('insertHTML'). The command returned true, which normally means success. But when Claude took a screenshot to verify, the new text had merged into the existing paragraph instead of creating separate paragraphs. The two new paragraphs had been shoved inline, right inside the "777-1 experiment" paragraph, creating a wall of jumbled text.

So Claude hit Ctrl+Z to undo. Took another screenshot. Confirmed the original structure was restored. And then it tried something different.

This is the part that honestly fascinates me. Claude discovered that Substack's editor (which uses a framework called Tiptap, built on ProseMirror) exposes its internal editor instance directly on the page. It accessed the editor through JavaScript, found a command called insertContentAt, and realized this was the correct approach. Here is why:

  • execCommand('insertHTML') inserts content inline within whatever paragraph the cursor is sitting in. It does not respect block-level boundaries.
  • insertContentAt places content between document nodes at an exact character offset. It creates proper, separate paragraphs.

Claude then walked the ProseMirror document tree, node by node, looking for the paragraph containing "777-1 experiment." When it found it, it calculated the exact character offset at the end of that paragraph: position 324. Then it used insertContentAt(324, htmlContent) to place the two new paragraphs with all their links and formatting.

It worked perfectly. Screenshot confirmed. Links verified through JavaScript. Italic words confirmed. Structure intact.

Saving Without Re-Sending

Claude clicked "Continue" to open the publish dialog, and this is where the email safety check happens. For an update to an existing post, Substack does not show a "Send via email" option at all. Claude clicked "MORE OPTIONS" just to be sure nothing was hidden, confirmed there was no email checkbox, and clicked "Update now."

The post went live with the edits. No email sent. Mission accomplished.

Claude then navigated to the live post, ran JavaScript to verify all 5 links had the correct URLs, confirmed both italic words rendered properly, and even zoomed in visually to double-check the styling.


Task 2: The Full Blog Post Migration

The Goal

This was the most complex task. The blog post where Allen Kendrick reintroduced himself had just been published on my portfolio. It needed to be migrated to Substack as a brand new post. That means: extract the full content from my website, convert all internal links from portfolio URLs to Substack URLs, preserve every piece of formatting (bold, italic, blockquotes, lists, code), insert two subscribe buttons at strategic locations, apply the correct tag, and publish with email delivery enabled so subscribers receive it.

Extracting the Source Content

Claude opened a second browser tab (keeping the Substack editor tab separate) and navigated to the post on my portfolio website. It used three different methods to extract the content, each for a specific reason:

  1. get_page_text grabbed the full article text in one call. Fast. Clean. But it does not show formatting.
  2. read_page read the accessibility tree, which revealed the DOM structure: which words were bold, which were italic, where the blockquotes and lists were, and all the <a> links with their relative URLs (like /blog/llm-instance-cloning-... and /ai-team/agent/igor-jarvis).
  3. innerHTML via JavaScript read the raw HTML in chunks. This gave Claude the exact markup it would need to reconstruct inside the Substack editor.

Why three methods? Because no single method gives Claude everything. Plain text loses formatting. The accessibility tree truncates long content. Raw HTML gives the full picture but needs to be read in pieces because of size limits. Each method fills a gap the others leave.

Building the Substack Post

Claude navigated to https://priscasolutionsai.substack.com/publish/post/new in the Substack tab. A blank editor. It set the section to "Portfolio Archive," typed the full title and subtitle, then focused the editor body.

Here is where the URL conversion happened. Before injecting any content, Claude applied Igor Jarvis's mapping table to convert every single link. Internal portfolio links like /ai-team/agent/allen-kendrick became https://priscasolutionsai.com/ai-team/agent/allen-kendrick. Blog post links like /blog/llm-instance-cloning-capture-ai-personality became their corresponding Substack URLs. Over 18 link conversions applied before a single character was injected into the editor.

Claude then injected the content in three parts using editor.commands.insertContent(), taking a screenshot after each injection to confirm proper rendering. After all three parts were in, it ran a cleanup script to check for empty paragraphs. Found zero. The injection was clean.

The Subscribe Buttons

This part is clever. Claude did not know the exact name for Substack's subscribe button in the Tiptap editor. So it asked. It ran JavaScript to list all available node types in the editor schema and found subscribeWidget. Then it inspected the widget's required attributes: url, text, and language, all nullable.

For each button, Claude walked the document tree to find the exact insertion position. Button 1 went after the Voice Matching section (position 6093). Button 2 went after the My Reaction section, before the Igor tease (position 12733). Both inserted using insertContentAt with the correct widget structure.

Publishing With Email

Claude clicked "Continue" to open the publish dialog. Two things needed fixing: the audience defaulted to "Paid subscribers only" and the comments were restricted. Claude changed both to "Everyone."

Then came the tag. Claude typed "Behind" in the tag field, saw the "Behind The Scenes" option appear, clicked it. But here is an amusing detail: when Claude clicked elsewhere to close the tag dropdown, it accidentally clicked the "No one" radio button for comments. It noticed the mistake on the next screenshot and clicked "Everyone" again. Even Claude makes accidental clicks.

For email delivery, Claude scrolled to the Delivery section and confirmed the "Send via email and the Substack app" checkbox was checked. This is correct for a new post. Subscribers should receive it. Claude clicked "Send to everyone now."

One more dialog appeared: "Add subscribe buttons to your post?" Substack detected that no built-in buttons were used. Claude clicked "Publish without buttons" because it had already inserted them manually through the Tiptap API.

Post published. Live. Confirmed.


Task 3: The Future Prisca Note

The Goal

"Claude God Tip #11: Meet Allen Kendrick" was published back in November 2025. Now that Allen had been reintroduced with his evolved 8-step workflow, this older post needed a Future Prisca Note telling readers: "Hey, Allen has grown since this was written. Here is where to see the full picture."

The note needed to be an H2 heading followed by an italic paragraph with a hyperlink. It needed to go in a very specific location: after the paragraph that says "Meet Allen Kendrick, my blog refining agent" and before "Here is everything you need to know about him." And like Task 1, this update should not re-send the post to subscribers.

Finding the Exact Spot

Claude navigated to the post, opened the editor through the three-dot menu (same process as Task 1), and then went straight to the ProseMirror document tree. No fumbling around this time. It walked every block node looking for the paragraph containing "Meet Allen Kendrick" and found it at position 3170, ending at position 3287.

But Claude did not just trust the number. Before inserting anything, it verified what existed at position 3287. It checked the nodes between positions 3170 and 3500 and confirmed:

  • Position 3170 to 3287: "Well you see... Meet Allen Kendrick, my blog refining agent." (correct)
  • Position 3287 to 3335: "Here is everything you need to know about him:" (correct boundary)
  • Position 3335 onward: The main Allen description paragraph (correct continuation)

The insertion point was 3287. Verified from both directions.

The Insertion

Here is what Claude built and inserted at position 3287:

An H2 heading: "Hey, Future Prisca Here! (March 5, 2026)"

Followed by an italic paragraph explaining that Allen had evolved from a one-step editor into an 8-step pipeline, and linking readers to his reintroduction post.

Notice something important: Claude used insertContentAt without even trying execCommand this time. It had already learned from Task 1 that execCommand merges content inline. By Task 3, that lesson was applied automatically. The failure in Task 1 prevented a failure in Task 3.

Claude scrolled to the insertion area, took a screenshot, and confirmed: the heading appeared at the correct level, the paragraph was italic, the link was underlined, and the original content below remained untouched. Clean.

Saving Without Re-Sending (Again)

Same process as Task 1. Claude clicked "Continue," and the dialog showed "Update now" instead of "Publish now," confirming this was an edit to an existing post. Claude scrolled to the Delivery section and verified the email checkbox was unchecked. The URL after saving contained alreadyPublished=true, which is Substack's way of confirming this was an update, not a new publication.

No email sent. Note inserted. Older post updated. Done.


The Pattern You Should See

If you read through all three tasks carefully, the same pattern emerges every single time:

Screenshots are how Claude sees. Every task starts with a screenshot. Every action ends with a screenshot. Claude takes a screenshot after navigating to a page, after clicking a menu, after injecting content, after publishing. If something unexpected happens (like the execCommand failure in Task 1 or the accidental radio click in Task 2), a screenshot is how Claude discovers the problem. It looks, it acts, and then it looks again.

JavaScript is how Claude acts precisely. Clicking is imprecise. It depends on screen coordinates, which change based on scroll position, window size, and page layout. JavaScript cuts through all of that. When Claude needs to find a specific paragraph, it walks the ProseMirror document tree programmatically. When it needs to insert content, it uses a Tiptap API call with an exact character offset. When it needs to verify links, it queries the DOM directly. JavaScript is the scalpel. Clicking is the finger.

ProseMirror's document tree is the surgical map. This is the piece that ties everything together. Every Substack post is internally represented as a tree of document nodes, each with a type (paragraph, heading, list item) and a position (character offset from the start of the document). When Claude calls doc.descendants(), it walks every node in that tree. When it calls insertContentAt(position, content), it places new content at an exact location in that tree. This is why the insertions are accurate down to the character. It is not guessing. It is not scrolling around looking for the right spot. It knows the position because it walked the tree and calculated it.

The Lesson From Task 1's Failure

I want to highlight something about the execCommand failure because it is actually the most important moment in this entire post.

Claude's first instinct was to try the simpler approach. execCommand('insertHTML') is a standard browser API. It works in many situations. And it returned true, which usually means success. But it failed silently. The content was inserted, but in the wrong structure. Text merged into the wrong paragraph. If Claude had not taken a screenshot to verify, it would not have known something went wrong.

That screenshot saved the task. Claude saw the problem, undid the change, and then explored the editor's internal API to find the correct approach. And by Task 3, it did not even attempt execCommand. The lesson was learned. Applied. Permanent.

This is the thinking loop in action. Not just "look, act, look again." But "look, act, look again, and if it did not work, find a better way."


What This Means for My Workflow

When Allen Kendrick finishes refining a blog post, he invokes Igor Jarvis to write a migration prompt. That migration prompt is what I paste into Claude in Chrome. And then, as you just saw, Claude handles the rest. It reads the content, converts the links, injects the HTML, places the subscribe buttons, sets the tags, manages the email settings, and verifies everything twice.

I walked away while all three of these tasks were running. I came back to three finished tasks and zero problems. The only reason I know what happened is because I asked Claude to tell me afterward.

That is the system. Allen edits. Igor writes the instructions. Claude in Chrome executes. And I get to focus on the thing I actually care about: writing the next post.


As always, thanks for reading!

Want to discuss this post?

Ask questions, share your thoughts, or join the conversation on Substack.

Read & Discuss on Substack

Continue Reading

Share this article

Found this helpful? Share it with others who might benefit.

Enjoyed this post?

Get notified when I publish new blog posts, case studies, and project updates. No spam, just quality content about AI-assisted development and building in public.

No spam. Unsubscribe anytime. I publish 1-2 posts per day.

Want This Implemented, Not Just Explained?

I work with a small number of clients who need AI integration done right. If you're serious about implementation, let's talk.