Horacio Herrera

Horacio Herrera

@horacioh.com

Building Seed (https://bsky.app/profile/seed.hyper.media), give people freedom to collaborate by saving the web!

122 followers68 following179 posts

So happy I learned about state machines & the actor model years ago. It's SO IMPORTANT in the era of AI agents and human/agent collaboration ❀️❀️❀️ AI agents are actors: messages, internal state, transitions. The actor model wasn't invented for AI but it might as well have been.

Me uno oficialmente como @DevinAI Ambassador πŸŽ‰ Llevo meses usando Devin en proyectos reales, no en demos. Ahora quiero compartir esa experiencia. Quick intro de lo que hago con Γ©l πŸ‘‡

Lo uso en producciΓ³n, a diario: @seed.hyper.media β€” mi plataforma de comunidades en red GymClass β€” herramienta para academias de artes marciales (horarios, pruebas, link-in-bio) Jean OSS β€” tooling open source

The hard part of building software is never the code. It's modeling what should happen β€” and what shouldn't. What's something in your projects where the *model* mattered more than the *implementation*? Reply with yours ↓

I used to test components. Now I test state machines. Component tests check 10 things you don't care about (React, DOM, CSS, timing). Machine tests ask one thing: given state + event, did it transition? You already have a state machine β€” make it explicit.

AI didn't just make code cheaper. It made maintainer attention more expensive. When anyone can generate a PR in 30 seconds, the bottleneck shifts: the scarce resource is review capacity, not code production. The best OSS contributions compress maintainer attention.

Local-first is not just offline mode. It changes who owns the product relationship: your work starts with you, survives the network, and syncs when the system catches up. That shift affects architecture, UX, and trust. It's a philosophy, not a feature.

Block identity is the most underrated problem in document editors. A block isn't just content β€” it's position, children, selection, version. Move it, split it, merge it β€” did the identity survive? Most editors miss this for months.

AI made code cheap. It didn't make knowing what to build easier. The bottleneck was never typing. It was understanding the problem, modeling abstractions, knowing what the user needs. AI didn't kill coding β€” it made visible what the real job always was.

Our designer asked me to make text formatting persist when you press Enter β€” if you're writing in bold and hit Enter, the next line stays bold. My first reaction: "yeah, that makes sense." Then I actually used it. Nope.

Bold carries over when I just wanted a heading. Italic keeps going when I wanted one emphasized phrase. Font family? Absolute chaos. Some styles should persist. Some shouldn't. Where's the line?

Help me settle this β€” what feels right to you? β€’ Formatting resets on every Enter (clean slate) β€’ Formatting always persists (whatever was active carries over) β€’ Only some styles persist (tell me which ones)

Your framework doesn't matter as much as your model. React, Vue, Svelte β€” they all render state. The hard part is knowing what states exist, which transitions are valid, and what happens when things go wrong. Good architecture is just good modeling with a nicer name.

Nick Nisi at WorkOS deleted 95% of their agent skills and got better results. The harness did the work. And a harness that isn't backed by explicit states and transitions is just a pile of if/else statements you're pretending is architecture. www.youtube.com/watch?v=vy7o...

youtube.comHow I deleted 95% of my agent skills and got better results β€” Nick Nisi, WorkOSWorkOS will be back for the World's Fair next week! see https://ai.engineer/wf and use YOUTUBEPROMO for new tickets only. Join 6000 AI engineers at the "Superbowl of AI"! --- Claude would fake…

Autosave looks simple until it isn't. "Just debounce a useEffect" works for v1 β€” then races, stale saves, and lost work crash the party. At that point the real model is not "an effect." It's a state machine. Explicit states + transitions = no stale IDs, no impossible UI.

My coding setup right now: Jean on local (web access), Jean on a server, Devin in the cloud. All reachable from a phone, tablet, or laptop through Tailscale. I could be anywhere. The agents don't know and they don't care. They're just working.

Boolean flags are fine until the product starts having memory. `isLoading` + `isError` + `isSuccess` + `isSaving` + `isRetrying` + `wasCancelled` = a state machine you're pretending isn't one.

I shipped 3 PRs this week while I was at BJJ. My agent wrote them. I reviewed when I got back. The code was fine. Not perfect β€” but neither is the code I write at 2am. The difference is I was on the mats, not at a keyboard.

We had 10 useEffect calls in a single React component. Our TipTap document editor was a mess of side effects fighting each other. Here's how we went from 10 β†’ 2 by embracing state machines and atomic hooks.

View full thread

The lessons: 1️⃣ Not every side effect belongs in useEffect 2️⃣ Extract hooks early β€” one concern = one hook 3️⃣ Let your state machine drive data flow, not effects 4️⃣ If you can count your effects on two hands, it's time to refactor

We had 10 useEffect calls in a single React component. Our TipTap document editor was a mess of side effects fighting each other. Here's how we went from 10 β†’ 2 by embracing state machines and atomic hooks.

View full thread

Step 5: Merge the rest into a single bridge effect. The remaining bits: - Register imperative handlers - Expose _suppressChangeRef for programmatic updates - Notify parent of editorReady All go in ONE effect that mounts once and cleans up on unmount.

We had 10 useEffect calls in a single React component. Our TipTap document editor was a mess of side effects fighting each other. Here's how we went from 10 β†’ 2 by embracing state machines and atomic hooks.

View full thread

Step 3: Extract atomic hooks. We pulled out: - useReadOnlyClickToEdit β€” handles click-to-edit with mousedown tracking - useBlockHighlight β€” block hover highlight state - useBlockBorderDebug β€” debug border toggle Each hook owns ONE concern. Testable. Clean.

Step 4: Use the state machine as the source of truth. We already had an XState doc machine for managing edit state. But it wasn't pushing content to the editor. We added an action that fires on document.remoteUpdate. Now the machine drives content sync, not an effect.

We had 10 useEffect calls in a single React component. Our TipTap document editor was a mess of side effects fighting each other. Here's how we went from 10 β†’ 2 by embracing state machines and atomic hooks.

View full thread

Step 1: Identify what each effect actually does. We categorized them: - Bridge effects (editor ↔ outside world) - Sync effects (keep state in sync) - Debug/Dev effects - Event handlers that shouldn't be effects at all

Step 2: Kill the fake effects. Turns out 3 were really just event handlers disguised as effects. - "Update editor content when prop changes" β†’ should be a ref + imperative handler - "Handle click-to-edit" β†’ literal DOM event listener They're events. Treat them as such.

We had 10 useEffect calls in a single React component. Our TipTap document editor was a mess of side effects fighting each other. Here's how we went from 10 β†’ 2 by embracing state machines and atomic hooks.

The problem: every feature added another useEffect. - Sync content? useEffect. - Handle focus? useEffect. - Track drafts? useEffect. - Debug overlay? useEffect. After a while, effects are running in non-deterministic order and stepping on each other.

I built a cron job so my wife wouldn't have to check a website every day. When I was almost done, she said: "You don't have to. I'm already used to doing it." That was a good reminder πŸ‘‡

Just because something can be automated doesn't mean it's painful enough for people to want it automated. Before building a solution, validate that you're solving a real painβ€”not just removing a repetitive task.

Considering Devin? Here's my honest experience after a month of real use. I'm a cofounder working full-time. I had a clear side project idea but zero time to build it myself. 🧡

A common refactor mistake: extracting JSX first. Splitting into <Header /> <Body /> <Footer /> helps readability β€” but the real complexity stays buried inside Body. Better order: extract rules β†’ test rules β†’ then split components if JSX is still noisy.

Rich text editor bugs hide in tiny interactions: click an image caption, type text, press Enter, verify the image wasn't deleted, check the next paragraph gets focus. That one test is worth 100 snapshots. Test the interaction.