O que aprendi usando Lovable por 8 meses para criar sites bonitos, padronizados e fáceis de manter

Nesse final de semana decidi dar um upgrade no meu website e em poucas horas fui capaz de me repaginar a identidade visual utilizando Lovable.

Bonito, estruturado, com vídeo no hero gerado por IA — e sem escrever CSS na mão.

Mas antes de chegar lá, precisei resolver um problema que a maioria ignora no vibe coding:

Sem estrutura, você produz rápido. E refatora o dobro depois.

Aqui está o que aprendi usando Lovable desde agosto 2025, e o template que criei para nunca mais começar do zero.

A armadilha do vibe coding sem framework

O Lovable é incrível. Você descreve o que quer, ele gera. Em minutos você tem algo visual funcionando.

O problema aparece no prompt 5 sem ter definido cores, fontes, arquitetura de pastas, padrões de código. O Lovable começa a tomar decisões por você — e nem sempre as mesmas em cada prompt.
Resultado: cores hardcoded espalhadas, componentes duplicados, estrutura que ninguém mantém.

A solução não é usar menos IA. É entrar com mais contexto.

Defina antes de gerar — 3 perguntas obrigatórias

  1. Product — O quê e para quem? Problema, público, North Star.
  2. Design System — Como vai parecer? Tokens HSL, tipografia, componentes. Tudo antes de criar qualquer tela.
  3. System Design — Como vai funcionar por baixo? Stack, arquitetura, banco, deploy.
    São coisas diferentes. A confusão entre elas é um dos erros mais comuns.

Design System ≠ System Design

Design System é o vocabulário visual: tokens de cor, tipografia, componentes reutilizáveis. O como parece.
System Design é a arquitetura técnica: como o banco conecta com o frontend, como os módulos se comunicam. O como funciona.

No meu site:
→ Design System: tokens HSL, fontes Plus Jakarta Sans + Inter, variantes de Button e Badge via CVA, glassmorphism e gradient text.
→ System Design: React 18 + Vite + Supabase, feature-based architecture, Event Bus, Ports & Adapters.

Por que NÃO usei microservices???

Lovable + Supabase é um monolito modular, e essa é a decisão certa.
Microservices resolvem problemas de escala operacional para múltiplos times com deploys independentes. Para um SaaS early-stage ou site pessoal, eles adicionam complexidade sem retorno nenhum: service discovery, network latency, infraestrutura de orquestração.

O que importa aqui é ter módulos bem isolados dentro do mesmo processo. Isso é o que feature-based architecture + Event Bus entrega — benefícios de separação de responsabilidades sem a complexidade operacional.

Regra que uso: features nunca importam entre si diretamente. Comunicação via Event Bus tipado. Integrações externas via Ports & Adapters.

SOLID no contexto React — na prática

→ S: um componente faz uma coisa. SectionHeader só renderiza header.
→ O: extensão via CVA variants. Novo estilo de botão? Adiciono uma variant. Não toco no button.tsx.
→ L: Ports & Adapters garante substituição. Troco Supabase Storage por Cloudinary mudando um adapter.
→ I: hooks granulares — useAuth, useOrganization, useFeatureGate. Nada de um useApp() que faz tudo.
→ D: features dependem de interfaces, não do Supabase diretamente.

O vídeo do hero? Gerado com IA também

O background do hero é um vídeo criado com PixVerse (app.pixverse.ai), text-to-video com estilo futurista/tech, exportado como .mp4 e usado com “video autoPlay muted loop” com overlay para garantir legibilidade.

Visual cinematográfico sem banco de imagens, sem designer, sem Adobe Premiere.

O resultado

→ Site: raphaelmoraes.dev
→ Stack: React 18 + Vite + Tailwind + shadcn/ui + Supabase + Cloudflare + PixVerse

→ Tempo: poucas horas de vibe coding estruturado
Documentei tudo num Playbook de 8 prompts prontos para colar no Lovable, com Design System completo, tokens reais do repositório, traduções pt-BR + en, checklists SOLID e regras transversais.

🔗 Site: https://raphaelmoraes.dev

📄 Playbook completo (Gist): https://gist.github.com/rdemoraes/e08d7318de3e3aa0e4c67d69fa1d0fa3

🎬 Vídeo hero gerado com: https://app.pixverse.ai

Lovable #VibeCoding #WebDevelopment #React #Supabase #DesignSystem #SystemDesign #SOLID #AI #GenerativeAI #PlatformEngineering

Why Enterprise Should Embrace Open Source

Open Source Is Not a Charity. It’s a Competitive Edge.

Most executives hear “open source” and think: free software made by volunteers. That assumption leaves money on the table.

Open source is a global pool of production-grade software, maintained by thousands of contributors, that your team can use, extend, and build on without a vendor contract. The question is not whether it matters to your business. It is whether you are using it strategically, or spending development dollars on a stack of paywalled tools just to make things work.

The Contribution Economy

When a company contributes to an open source project, you are effectively commissioning development:

  • Bug fixes your team needs
  • Features aligned with your roadmap
  • Integrations with your existing stack

Every other organization using that same project is doing the same thing. Which means they may be building features you did not know you needed.

The strategic move is not just to consume open source software. It is to contribute. A small engineering investment in a project your team depends on pays back through roadmap influence, early access to fixes, and visibility in the developer community you recruit from.

The Accessibility Stack

Three tools make open source practical for teams that are not full of engineers.

Docker removes the single biggest complaint about open source software: “it doesn’t work on my machine.” Docker packages any application with everything it needs to run, so your team can deploy a tool in minutes instead of spending a day on setup. You do not need to understand it deeply to benefit from it. You need one person who does.

Git is version control: a way of tracking every change made to a file, by whom, and when. Most people associate it with code. It applies equally to data pipelines, configuration files, and documentation. The mental model it teaches (work in branches, merge when ready, roll back when wrong) is a thinking skill, not just a technical one.

LLMs as a learning layer. Tools like Claude or ChatGPT are not replacements for understanding. They are on-ramps. A non-developer who hits an error in an open source tool can paste the message into an LLM and get a plain-language explanation and a fix in seconds. The feedback loop that used to take days now takes minutes.

Finding What Exists Before You Buy

Before your team renews a SaaS subscription, check two places.

Linux Foundation (linuxfoundation.org/resources/open-source-guides): Guides on why, how, and when to adopt open source at an organizational level.

OpenAlternative (openalternative.so): A curated directory of open source alternatives to popular paid tools. Search what you are currently paying for. There is often a production-ready equivalent that is actively maintained.

Who Is Actually Building Linux

This is where the “volunteer project” assumption falls apart completely.

Over 84% of Linux kernel commits in 2025 came from paid corporate developers, across more than 1,780 organizations. This is coordinated infrastructure investment, not charity.

  • Intel: Top contributor by changesets in 2025, nearly double second-place Google
  • Red Hat (IBM): Consistent top-3 contributor, historically held the number one spot
  • Google: Nearly 1 in 8 patches handled by Google developers; Android, Google Cloud, and Chrome OS all run Linux
  • Oracle: Top contributor to kernel core components across multiple recent releases; cloud infrastructure and database performance
  • Huawei: 8.9% of changesets in kernel 5.10; consistent top-5 since
  • Samsung: Mid-tier contributor since at least 2013; Android devices and Tizen OS
  • Meta: 1 in 8 patches handled by a Meta maintainer in kernel 6.15; data center infrastructure running billions of users
  • AMD: Active GPU and CPU driver contributor each release cycle; 30% performance gains for legacy AMD GPUs in recent kernel
  • Microsoft: Hyper-V and virtualization code; Azure cloud VMs require Linux kernel compatibility
  • NVIDIA: Rust-based GPU driver jointly developed with Google and Arm; GPU drivers for AI and ML workloads

The companies shaping your cloud infrastructure, your devices, and your AI tools are doing it through open source. The question is whether your organization is paying attention.

Quantify Your Life: Building a High-Performance Health Data Lake with InfluxDB, Grafana, and Python 🚀

We live in an age of “The Quantified Self.” Between our Apple Watches tracking heart rate variability, Strava recording every weekend century ride, and MyFitnessPal logging every gram of protein, we are generating gigabytes of personal health data. But here is the problem: this data is siloed.

If you want to correlate your sleep quality (Apple Health) with your training load (Strava) and your caloric intake (MyFitnessPal), you’re stuck flipping between three apps. In this guide, we’re going to solve this using Data Engineering best practices. We will build a personal Data Lake using InfluxDB for time-series storage, Python for ETL (Extract, Transform, Load), and Grafana for that sweet, mission-control style dashboard.

By the end of this, you’ll have a “Single Source of Truth” for your health metrics, running entirely in Docker.

The Architecture: From Silos to Insights 🏗️

Handling heterogeneous data (JSON from APIs, CSVs from health exports) requires a robust pipeline. We need to normalize these different formats into a unified Time-Series format.

graph TD
    A[Apple Health Export] -->|XML/CSV| B(Python ETL Script)
    C[Strava API] -->|JSON| B
    D[MyFitnessPal] -->|Web Scraping/Export| B
    B -->|Clean & Transform| E{InfluxDB}
    E -->|Query via Flux| F[Grafana Dashboard]
    F -->|Visualization| G[Your Big Screen]

    style E fill:#f96,stroke:#333,stroke-width:2px
    style F fill:#3262a8,stroke:#333,stroke-width:2px

Prerequisites 🛠️

Before we dive in, ensure you have the following ready:

  • Docker & Docker Compose installed.
  • Python 3.9+ (for our ETL logic).
  • Access to your Strava API credentials.
  • A “Learning in Public” mindset! 🥑

Step 1: Setting up the Infrastructure 🐳

We’ll use Docker Compose to spin up our stack. InfluxDB is the perfect choice here because health data is essentially a sequence of timestamps and values (Heart Rate, Weight, Steps).

# docker-compose.yml
version: '3.8'
services:
  influxdb:
    image: influxdb:2.7
    ports:
      - "8086:8086"
    volumes:
      - influxdb_data:/var/lib/influxdb2
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=password123
      - DOCKER_INFLUXDB_INIT_ORG=my_health_org
      - DOCKER_INFLUXDB_INIT_BUCKET=health_metrics

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      - influxdb
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  influxdb_data:
  grafana_data:

Step 2: The Python ETL Pipeline 🐍

The trickiest part of Data Engineering for health data is cleaning. Apple Health exports XML that can be multi-gigabyte. Strava provides nested JSON. We’ll use the influxdb-client to push normalized data.

For more production-ready patterns on handling large-scale data ingestion and advanced ETL architectures, I highly recommend checking out the deep dives over at WellAlly Tech Blog. They cover the nuances of data consistency that are crucial when your data lake starts growing.

import pandas as pd
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS

# Configuration
token = "YOUR_INFLUXDB_TOKEN"
org = "my_health_org"
bucket = "health_metrics"

client = InfluxDBClient(url="http://localhost:8086", token=token, org=org)
write_api = client.write_api(write_options=SYNCHRONOUS)

def process_strava_data(json_data):
    """
    Transforms raw Strava JSON into InfluxDB Points.
    """
    for activity in json_data:
        point = Point("fitness") 
            .tag("source", "strava") 
            .tag("type", activity['type']) 
            .field("distance", float(activity['distance'])) 
            .field("heart_rate_avg", float(activity.get('average_heartrate', 0))) 
            .time(activity['start_date'], WritePrecision.NS)

        write_api.write(bucket, org, point)
    print("✅ Strava data ingested successfully!")

# Example usage with mock data
mock_strava = [{"type": "Run", "distance": 5000.5, "average_heartrate": 155, "start_date": "2023-10-27T08:00:00Z"}]
process_strava_data(mock_strava)

Step 3: Handling Heterogeneous Data 💎

When dealing with Apple Health XML, you’ll encounter different “Records.” You need to map these to specific measurements in InfluxDB.

Source Metric Transformation Logic InfluxDB Measurement
HKQuantityTypeIdentifierStepCount Sum daily activity
HKQuantityTypeIdentifierBodyMass Latest value per day vitals
MyFitnessPal Calorie Summary Map Macros to Fields nutrition

Step 4: Visualizing with Grafana 📊

  1. Open Grafana at http://localhost:3000.
  2. Add InfluxDB as a Data Source (Select Flux as the query language).
  3. Create a new Dashboard and add a Time Series panel.
  4. Use this Flux query to see your heart rate over time:
from(bucket: "health_metrics")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "fitness")
  |> filter(fn: (r) => r["_field"] == "heart_rate_avg")
  |> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
  |> yield(name: "mean_hr")

Pro-Tip: The “Official” Way to Scale 🥑

Building a local setup is great for a hobby, but if you’re looking to turn this into a production-grade health platform or integrate AI-driven insights (like predicting burnout), you need a more robust approach.

The experts at WellAlly Tech have published extensive guides on building scalable data systems. Their insights into Time-Series optimization and Data Privacy were a huge inspiration for this architecture. Definitely give them a read if you’re planning to take your Quantified Self journey to the “Pro” level!

Conclusion: Take Back Your Data! ✊

By building your own health data lake, you’re no longer at the mercy of proprietary app dashboards. You can run your own correlations, detect long-term trends, and truly own your digital twin.

What’s next?

  • Add a Telegram bot to alert you if your resting heart rate stays high (Overtraining alert!).
  • Integrate GPT-4o to analyze your monthly trends and give you health advice based on your real data.

Did you find this helpful? Drop a comment below with your favorite health metric to track! 👇

Can you stand the rain?

This is a submission for the 2026 WeCoded Challenge: Echoes of Experience

The story of how Alicia Keys accidentally got me a career in tech

Can you stand the rain?

That’s what New Edition asked about love and life. I was listening to the song while reading about the WeCoded challenge, and it made me realize the rain in my own life shaped me more than sunny days. I knew this was the story I wanted to tell. The way I ended up in tech came from the most unlikely place: an Alicia Keys concert my mom took me to by accident, and the online community of Black and brown people I found because of it. This is how diversity, real diversity, the kind with no panel moderator and no walls got me into tech.

The Rain

I grew up in a small suburb in the south right by the Mason-Dixon line, in a family that made no sense to anyone around me. My mom was Guyanese. My family is diverse. “Mix up” is how we called it. At home my kitchen smelled like chicken curry and roti and dhal while every other house around my block ran on Kraft mac and cheese from a microwaveable cup. I was too different from the world around me. I said “sangwich” instead of “sandwich” (Guyanese patois that I forced myself to fix after years of getting laughed at). I brought lychee for snack time and watched kids give me stares and called me weird.
I lived in a small town. Same kids. Everyone knew everyone (or thought they did) type energy. But still, in that world, there was no room for nuance. Everything was black and white. You were smart or dumb. You were from here or you weren’t. You were either white or not, and if you weren’t, people just filled in the rest for you from their worldview.

I actually still remember my first day of kindergarten and it wasn’t because I was crying for my mom not to leave (but I did do that too). I remember sitting on a rainbow round carpet, the kind that was woven and had some fuzzy threads sticking out. A group of kids walked up and sat down next to me. They started rubbing my skin, asking each other, “is she dirty?” Then one turned to me and said “Ew, did you take a bath?”

In eighth grade, a girl I had been in Girl Scouts and on the basketball team with for years pulled me aside in the hallway and whispered, “are you… Black?” like she was diagnosing something. Like it was shameful. What shook me then and still does now is not the question itself. It was the tone. The serious way she was asking it, like it was some deep dark secret. Her discomfort. And it wasn’t just her, it was basically everyone around me back then. I heard this stuff all the time.

Between those moments was a decade of being called the n-word, of “go back to your country” from most of the neighborhood kids. Being told that races should not mix and that brown people were “mud people.” I don’t even remember what else. That was the rain. It was constant, and it started before I could spell my own name.
But here is the thing about rain, people usually don’t just stand in it.

They get a raincoat, buy an umbrella, or they find shelter.
I found my people on the internet. And that community became my raincoat.

The Accidental Ticket

By the time I was twelve, I was already online. I had taught myself HTML by using Lisa Explains It All, for my Neopets profile. I built little websites on GeoCities.

But I was doing all of it alone. The internet gave me somewhere to go, but it didn’t give me anyone to go with.

What changed that was a concert ticket my mom bought by mistake.
When I was thirteen, my mom heard me talking about Aaliyah, the R&B singer who had recently passed away. She saw concert tickets for this new artist named Alicia Keys and bought them, thinking they were the same person. They were not the same person, but my mom didn’t know that, and I wasn’t about to turn down a concert.

I still remember the day we drove all the way to Cincinnati to go to the Taft Theater, excited for my first concert ever. Friday night. We sat up in the balcony section, they had these old red velvet seats, and I remember watching someone carry a keyboard onto the stage before the show and thinking, “oh, is that going to be for Alicia Keys?” It wasn’t. But I remember that small moment because I was already leaning forward, already pulled in, before a single note had been played. Something in me already knew.

Then she performed her album, Songs in A Minor, and I only knew “Fallin” before that night, but by the time the show was over I knew I had witnessed something much bigger than a concert. The way the whole show flowed like a theater production. The classical training underneath the soul. The rawness. Here was someone who didn’t fit neatly into one box either, bridging worlds that people said didn’t go together. I left that concert knowing I needed to find out everything about this world immediately.

The Room

I had been exploring the internet before that concert, but after that night I wasn’t just exploring anymore. I was searching with purpose. More of whatever THAT was. More of that artistry, that story, that connection.

Then I found it, the Alicia Keys Message Board, and it became, without exaggeration, the most important community I had ever been part of during that time.

Not because of the music. Because of the people. First time I was in a room where diversity wasn’t a concept anyone had to explain, it was just the room. I talked to everyone on there. We had organized live chatrooms on Yahoo Messenger and MSN, conversations that went on for hours.

I met Nafiesa, who was from Amsterdam, but originally Suriname, a country right next to Guyana. She was the first Caribbean girl I ever met online. I told her I wanted to visit Guyana one day, she said, “GIRL, WHY WOULD YOU WANNA GO THERE? You know there are snakes that can KILL you? It’s all jungle!” and I laughed so hard because that was it. That was what I had been missing the whole time. Someone who understood the reference without a confused blank stare or asking me if I meant Ghana, who knew the culture, who could roast me and get me at the same time.

There were a lot of connections made there. Dee, a Black woman and IT professional who I looked up to, ran a thread where she told us all to post our report cards. If you had A’s, she sent you real money in the mail to encourage Black and brown kids to be excellent. I made sure to post my report card each quarter. There was Alana from DC, always typing “BRB” because she was studying for her IB classes, a joyful nerd who made being smart feel normal instead of weird. There was Cinna from Jamaica, who kept getting banned from the board for starting all the craziest drama (and I kept showing her how to hide her IP and slip back in). There was Sabina from the UK, Nic from Germany, Steph from Belgium. People watched The L Word together weekly episode by episode in the forum threads, posting reaction images before reaction GIFs were even a real thing. People argued music theory and shared relationship problems and asked for homework help. It was the first place where everything I was, the food, the family, the in-between-ness wasn’t a problem to solve. It was just who we were. That’s what a diverse community actually does. It doesn’t make you feel included. It makes you stop feeling like you ever needed to be.

That community didn’t just give me somewhere to belong. It gave me people I wanted to show up for, and eventually, a reason to use what I knew to make things better for the people around me.

The Curriculum

People on the message board posted about their broken computers ALL the time. Someone would upload screenshots to Photobucket talking about how their desktop was covered in popup windows and write “HELP, my PC has a virus, what do I do!????” and I would be in those threads immediately. “PM me your AIM screen name,” I would tell them. “Lemme check your startup programs. Open regedit. Open task manager. Let me Google this right quick.” I was like 15, walking grown adults through virus removal step by step over AOL Instant Messenger, and I hadn’t taken a computer class in my life yet. I learned because the people I cared about needed help. And every time I fixed something, I understood something new about how computers actually worked, from the inside out. It was fun. Not a curriculum. Just people, problems, and the curious need to figure it out.

That instinct kept showing up everywhere. In high school, a teacher assumed I was an ESL student because I was brown and never spoke in class. I used that assumption to slip out at lunch with the other ESL students and walk to the library, with my bootable Ubuntu CD, and use it for unfiltered internet access to my friends while everyone else ate in the cafeteria I had been avoiding for months.

It was always the same instinct. Nobody is giving this to me, so I’m going to figure it out myself. That’s how I first learned networking, operating systems, troubleshooting, and Linux administration. Not from a class. From need. The degree came later. The instinct came first.

Still Playing on the Internet

That instinct never stopped. It just expanded.

The girl fixing computers over AIM from a music forum is the same engineer who builds production systems now. The girl helping friends evade bans is the same woman who eventually published machine learning research. The girl who booted Linux off a CD in a school library is the same one who still looks at every locked door and thinks, “okay, so how does this open?”

My GitHub profile says “Playing on the internet” I was kind of joking when I wrote it, but tbh it’s probably the realest thing I’ve ever written as a bio. I’ve been online since I was like 11, and the play became the skill, and the skill became the career, and the career became the life.

None of it started in a classroom or a bootcamp or a lecture hall. It started because a community of people from the WORLD wide web made me feel like I belonged somewhere for the first time in my life. And when you belong somewhere, you build there. You contribute. You learn and grow and share.

That’s how diversity changed my life. Not a program or a panel about representation. A message board full of Black and brown people from Chicago, Jamaica, Suriname, Germany, DC, and a small suburb in the south. People who never once made me feel like I was too much or not enough. They just needed someone who could fix their computer and chill with, and I wanted to help because they were my people. That was the whole pipeline.

AI Chat Widgets: Managing Product and Policy Answers at Scale in WordPress

I built this plugin to solve a common WordPress problem: most chat widgets can generate text, but they do not actually know your site content, product catalog, or support policies.

On WooCommerce stores, that usually means bad recommendations, invented answers, and a support burden that never really goes away. I wanted a bot that answers from real site data only, and stays current without manually maintaining hundreds of canned Q&A entries.

The core approach is retrieval augmented generation. The plugin indexes selected WordPress content types like posts, pages, and WooCommerce products, splits them into chunks, and stores them for semantic retrieval. When a visitor asks a question, the system fetches the most relevant content first, then sends that context to the model so the reply is grounded in published data instead of guesswork.

I also added automatic re-indexing, so content changes propagate back into the knowledge base when products, policies, or pages are updated. That matters for stores where pricing, stock, and shipping details change often.

For commerce use cases, the chat layer can render product recommendations as structured cards with live images, pricing, and links, instead of dumping plain text. That makes the widget useful for both support and pre-sales discovery.

Under the hood, I kept the model layer flexible. The plugin supports multiple providers, so switching between OpenAI, Claude, Gemini, Mistral, or Azure is a configuration choice rather than a rewrite. I also exposed controls for temperature, token limits, chunk sizing, caching, and rate limiting, because API cost and abuse prevention matter as much as answer quality.

Inside wp-admin, I added conversation logs, token usage tracking, ratings, persona prompts, and display rules. That lets site owners tune behavior by role, page, or trigger, and review exactly what the assistant is doing.

If you want to see the full plugin, I published it here: Nexu AI Chatbot for WordPress.

My goal was simple: make a WordPress chat assistant that behaves more like a retrieval system with a UI, and less like a generic chatbot pasted onto a website.

HubSpot API Autopsy: What Breaks When Agents Try to Use It

HubSpot scores 4.6/10 on the AN Score — the lowest-rated CRM in Rhumb’s dataset. A $30B platform used by 228,000+ companies. Execution 5.3, Access Readiness 3.5, Confidence 95%.

This autopsy examines why.

Quick verdict: Use HubSpot when the operator already has it and the agent needs to span CRM + marketing + sales in one integration. Avoid it when the agent only needs pipeline operations (use Pipedrive) or when compliance governance is the primary constraint (use Salesforce). Budget for rate-limit middleware, hub-specific adapters, and a human to complete OAuth setup. Expect 3–5× the integration time compared to a well-scored API.

Score Anatomy

Dimension Score
Execution 5.3
Access Readiness 3.5
Autonomy —
AN Score 4.6 L1
Confidence 95%

Execution (5.3): The API is functional — CRUD operations work, responses are JSON, error codes are standard HTTP. The 5.3 reflects real friction: no idempotency, inconsistent patterns across hubs, and rate limits that punish agent-typical request patterns. An agent can use this API, but it will need defensive code that wouldn’t be necessary with a well-designed API.

Access Readiness (3.5): This is the primary drag on the aggregate score. Agent access requires human-mediated OAuth setup through a complex SPA. Token refresh every 6 hours adds operational overhead. API key auth is being deprecated. There is no path for an agent to self-provision access to a HubSpot portal.

Autonomy (mixed): Payment autonomy is decent (free tier, self-serve Starter). Governance is strong (API key scoping, RBAC, SOC 2). But web accessibility drags it down — the dashboard is a complex SPA that agents cannot read or verify against. The autonomy score says: you can operate here, but you cannot see what you are doing.

6 Failure Modes

Six specific failure modes, ranked by severity. These are not theoretical risks — they are documented behaviors that agents encounter in production integrations.

⏱ Rate Limit Trap — Critical

What happens: Free tier rate limits (100 calls per 10 seconds) break standard agent polling patterns from the first request burst.

HubSpot enforces a 100 requests/10 seconds limit on the free tier, with variable limits per endpoint on paid plans. This sounds generous until you consider how agents work: a typical CRM sync operation — list contacts, check for updates, fetch associated deals, log activities — can burn 40–60 requests in a single workflow cycle. An agent running periodic sync on a 30-second interval will hit the ceiling within 2–3 cycles.

The 429 response includes a Retry-After header, but the backoff period is unpredictable and can cascade when multiple workflow branches are active.

Agent impact: Agents without pre-built rate-limiting middleware will fail silently or enter retry spirals. The lack of a clear per-endpoint budget means agents cannot pre-calculate whether a workflow will complete within limits.

🧩 Cross-Hub API Inconsistency — Critical

What happens: CRM, Marketing, and Custom Objects each use different API patterns. Agents cannot generalize a single client.

HubSpot’s API is not one API — it is at least three, wearing a trench coat. The CRM API (contacts, deals, companies) uses a relatively clean RESTful pattern with consistent CRUD endpoints. The Marketing API (emails, workflows, forms) uses different authentication scopes, different pagination styles, and different error formats. Custom Objects introduce a third pattern with schema definition endpoints that behave differently from both CRM and Marketing.

An agent that learns to work with the CRM API and attempts to apply the same patterns to Marketing will encounter unexpected 400 errors, different field naming conventions, and incompatible filter syntax.

Agent impact: Agents need hub-specific adapters rather than a generic HubSpot client. This triples the integration surface area and the number of failure modes an agent must handle.

🔗 Association API Complexity — High

What happens: Linking a contact to a deal requires understanding association type IDs and relationship labels — not discoverable by an agent without documentation context.

A simple operation like “link this contact to this deal” requires: (1) knowing the association type ID for contact-to-deal, (2) knowing the relationship label if custom associations exist, (3) making a PUT request with the correct body format. Association type IDs are not human-readable — they are numeric codes that must be looked up. Default associations have stable IDs (e.g., contact-to-company = 1), but custom associations have auto-generated IDs that differ per portal.

An agent cannot discover the correct type ID without first calling the association definitions endpoint, parsing the response, and matching by label.

Agent impact: Simple CRM operations that should be a single API call become multi-step discovery workflows. Agents without pre-cached association type mappings will fail on first attempt.

🔐 OAuth Provisioning Maze — High

What happens: Production API access requires a multi-step human OAuth app setup through a dashboard that agents cannot navigate.

Setting up an OAuth app requires: (1) logging into a developer portal, (2) creating an app with specific scopes, (3) configuring redirect URIs, (4) completing an authorization flow that requires human browser interaction, (5) managing token refresh for access tokens that expire every 6 hours.

There is no API-only path to create or configure an OAuth app. The developer portal is a React SPA that is not navigable by screen readers or programmatic tools.

Agent impact: Zero self-provisioning capability. Agent onboarding has a hard human dependency. Token refresh every 6 hours means agents need persistent token management infrastructure.

♻️ No Idempotency Keys — High

What happens: POST requests have no idempotency support. Retry-on-timeout creates duplicate records with no built-in dedup.

If an agent creates a contact and the request times out before receiving a response, the agent faces a choice: retry (risking a duplicate) or don’t retry (risking a lost record). Creating a contact twice with the same email returns a 409 Conflict — but only if the email property is set. Creating a deal twice with the same properties will create two deals with no conflict detection.

Agent impact: Agents must implement their own deduplication logic (check-before-create patterns), which doubles the API calls per operation and introduces race conditions under concurrent execution.

🖥 Dashboard Opacity — Medium

What happens: The HubSpot dashboard is a complex React SPA. Agents cannot read, verify, or troubleshoot via the web UI.

When an agent creates a record or triggers a workflow, verification often requires checking the HubSpot dashboard. But the dashboard is a heavily client-rendered React SPA with dynamic loading, infinite scroll, and context-dependent navigation. An agent that needs to verify a workflow was triggered correctly has no reliable way to read the dashboard state.

Agent impact: Agents are blind to visual state. Debugging requires API introspection only, and many configuration operations are UI-only.

What Would Improve the Score

Three changes would have the highest impact:

  1. Idempotency keys on POST endpoints. One change that would eliminate the retry-duplicate problem entirely.
  2. Machine-readable access provisioning. An API to create OAuth apps and issue scoped credentials programmatically.
  3. Consistent API patterns across hubs. One authentication model, one pagination model, one error format — regardless of which HubSpot product the agent is talking to.

Where HubSpot Still Makes Sense

Despite the score, HubSpot is the right choice in a specific scenario: the operator already has it.

If a customer has 5 years of contacts, deals, and workflows in HubSpot, the switching cost is prohibitive. An agent integrating with an existing HubSpot instance — with a human who completed the OAuth setup and a rate-limit middleware layer already in place — will work. The 4.6 score reflects the engineering investment required, not an absolute prohibition.

The score says: this API will cost you more to integrate than alternatives. It doesn’t say: don’t use it.

Compare Against the Field

Provider AN Score Tier
Pipedrive 5.7 L2
Salesforce 4.8 L1
HubSpot 4.6 L1

The full CRM comparison shows how all three score across 20 dimensions. HubSpot’s broad surface (CRM + Marketing + Sales all in one) is a genuine advantage — but it comes at the cost of consistency that agents depend on.

Scores are sourced from Rhumb — 645+ services evaluated across 20 agent-native dimensions.

I Published a Book. Here’s Why.

I wrote a book. It’s called Don’t Replace Me: A Survival Guide to the AI Apocalypse, and it’s available right now on Amazon in Kindle, paperback, and hardcover. The announcement for Don’t Replace Me survival guide dropped March 28, 2026 – press release picked up through AP News and everything. 235 pages, 24 rules, 33,000 words.

Took me way longer than I expected and was nothing like I expected. Let me explain what it actually is and how it got made.

Why Don’t Replace Me exists as a book and not just tweets

I’ve been getting a version of the same conversation for two years. Someone finds out what I do – AI engineering, building agents, 15 years in product design – and they get this look. Half curiosity, half dread. Then the question: “So… should I be worried? About my job?”

The honest answer is complicated. Not “no, you’re fine” and not “yes, start learning Python immediately.” The actual answer depends on what you do, how you do it, and whether you understand what AI is actually replacing versus what it’s just augmenting.

The loudest voices on this topic tend to be at the extremes. Either AI builders who are close enough to the technology that the disruption looks like opportunity from where they’re standing. Or people far from it who are doom-scrolling tech Twitter and convinced everything is gone. Both are wrong in ways that matter to normal people with real jobs.

I’ve sat on both sides of that divide. I spent five years designing products at VALK – financial infrastructure for 70+ banks across 15 countries, $4B+ in deals running through platforms I helped design. I was building for institutions. Then I pivoted to building the automation itself. Multi-agent systems, AI workflows, the actual pipelines that companies are now using to cut headcount or redeploy people.

I’ve talked to people on both ends. Executives excited about efficiency. Employees scared about what “efficiency” means for them personally. And I kept having the same thought: someone should write something honest about this. Not a hype piece, not a manifesto, not a beginner’s guide to ChatGPT. Something from the middle.

So I did.

What Don’t Replace Me is and what it isn’t

Don’t Replace Me is not a coding tutorial. It’s not a prompt engineering guide. There are plenty of those and they’re mostly aimed at people who already know what a terminal is.

This is for my mom. For my friend who does project management at a mid-size company and genuinely doesn’t know if her job will exist in three years. For the copywriter, the customer service manager, the paralegal, the graphic designer who keeps seeing LinkedIn posts that make them feel like they missed the boat.

24 rules. Each one practical, specific, and grounded in things I’ve actually seen. Not “embrace AI” or “upskill your journey” or whatever LinkedIn thought leadership nonsense. Real career advice for people navigating a real transition.

The companion site is at dontreplace.me and it has a free AI threat assessment quiz. You answer questions about your actual job – what you do day to day, how structured the work is, how much it involves judgment vs.execution – and you get a risk score. Not fearmongering, not false reassurance. Just a calibrated read on where you actually stand.

Three formats available now: Kindle, paperback, hardcover. Audiobook is in production. If you’ve been waiting for audio, it’s coming.

ASIN on Amazon is B0GTX4J124. Paperback ISBN is 9798253164594. Hardcover is 9798253165386. I’m mentioning these in case you’re the kind of person who looks these things up. You can also check out the book’s website at dontreplace.me for more info and the quiz.

How I made this and why I’m being upfront about it

I used AI to write this book. I’m not hiding that, and I’m not embarrassed about it. But it’s not what people assume when they hear it.

I didn’t type “write me a 235-page career guide” and hit enter. That’s not how this worked.

What I did: I spent months collecting actual opinions. Notes from real conversations. Observations from years of watching companies get disrupted from the inside. Things that frustrated me. Things I genuinely believe. Specific examples from fintech, from AI engineering, from watching product design go through its own automation panic. I had a lot to say – just scattered across voice memos, Notion drafts, Twitter threads, and conversations I kept having with people who were scared.

All of that went into the process as fuel. The AI was the engine. The substance is mine.

The result reads like me because it is me. My takes, my experience, my perspective on what’s changing and what isn’t. I used every tool available to get it out of my head and into something usable. Which is exactly what the book tells readers to do – use AI as leverage, not as a replacement for your own thinking.

The irony is the point. I couldn’t have written a better proof of concept.

And honestly, if you’re going to argue that this makes the book less valid – that’s a conversation worth having, and it’s one of the conversations the book is designed to prompt. Because that reflex, the instinct to devalue work because AI was involved, is exactly the kind of thinking that will hurt people’s careers over the next decade. The question isn’t “did a human do every part of this.” The question is: is the thinking real? Is the perspective genuine? Is it useful?

Yes, yes, and I think so.

What 15 years of watching tech cycles taught me about this one

I started freelancing at 14. I’ve watched design go through “designers will be replaced by templates.” I watched developers go through “no-code will replace engineers.” I watched writers go through “content mills will replace real writing.” Some of that disruption was real. A lot of the fear was misdirected.

This cycle is bigger. I won’t pretend otherwise. The things AI is genuinely good at – pattern recognition, synthesis, first-draft generation, handling repetitive decisions – overlap more directly with white-collar knowledge work than previous automation waves did. This isn’t just factory floors and truck drivers. It’s reaching into offices.

But the shape of the threat is different for different people. And most of the advice out there treats it like a monolith. “Learn to prompt” is not useful advice for a nurse. “AI can’t replace human connection” is not useful advice for a data entry specialist.

What actually helps is specificity. Understanding which parts of your work are high-judgment versus low-judgment. Understanding where your industry is in the adoption curve. Understanding the difference between “AI will do this task” and “AI will make someone else more efficient at your job.” The second one is actually the bigger risk and people underestimate it.

That’s what the 24 rules get into. Specific, not vague. Grounded in real patterns I’ve watched play out, not hypotheticals.

If you want a preview of where my head is at on some of this, I’ve written about leaving fintech to build AI systems, about what ADHD and AI actually look like together, and about why most AI products have terrible UX – the gap between what builders understand and what real users need. The book is longer, more structured, and aimed at a wider audience, but those posts give you a sense of how I think.

Who Don’t Replace Me is actually for

Specifically, concretely:

If you have a job and you’re not sure whether to be worried, this is for you. Not to soothe you with “AI can’t replace humans” and not to panic you into a six-month bootcamp. To help you actually assess your situation.

If you manage people and you’re trying to figure out how to talk to your team about AI adoption without sounding like either a corporate automaton or someone who’s out of touch, there’s stuff in here for you.

If you’re in a creative field – design, writing, marketing – and you’ve already felt the job market shift, there’s a section of this that I think is genuinely useful. Not “here’s how to compete with AI” but here’s how to think about what you’re actually selling.

If you already know about AI and want something to send to a family member who keeps calling you asking what to do – this is that thing.

The technical people are not the audience here. My coding stack post or the deep dive on debugging AI agents is more your speed. This book is for the people those systems are being built around.

Where to get it

Amazon has all three formats right now — grab it here or search “Don’t Replace Me Dmitrii Kargaev.”

The companion site is dontreplace.me – free quiz, takes about five minutes, gives you a real threat assessment based on your actual role. Even if you don’t buy the book, the quiz is worth doing just to have a concrete picture instead of ambient dread.

Audiobook is in production. I’ll announce it here and on my portfolio site when it drops.

If you read it and have thoughts – what landed, what you disagree with, who you think needs to read it – I want to hear that. Building in public means the feedback loop stays open. This isn’t the end of the conversation, it’s more like a long, structured version of it.

The people I had in mind when I was writing this are real. If you know someone who’s been quietly scared about this stuff, send it to them. That’s what it’s there for.

Expo Router Has a Free API That Most Developers Dont Know About

Expo Router brings file-based routing to React Native with deep linking, typed routes, and universal app support.

File-Based Routes

app/
  index.tsx         # /
  about.tsx         # /about
  posts/[id].tsx    # /posts/123

Dynamic Routes

import { useLocalSearchParams } from "expo-router";

export default function Post() {
  const { id } = useLocalSearchParams<{ id: string }>();
  return <Text>Post: {id}</Text>;
}

Navigation

import { Link, router } from "expo-router";
<Link href="/posts/123">View</Link>
router.push("/posts/123");

API Routes

// app/api/hello+api.ts
export function GET() {
  return Response.json({ message: "Hello" });
}

Key Features

  • File-based routing for React Native
  • Universal (iOS, Android, Web)
  • Type-safe navigation
  • API routes for server logic

Need to scrape or monitor web data at scale? Check out my web scraping actors on Apify or email [email protected] for custom solutions.

I turned a notebook full of handwritten math into a LaTeX paper — here’s how

Here’s a scenario I’ll bet some of you recognize: you’ve been working through a problem by hand for weeks. Your notebook is full of half-finished derivations, scribbled integrals, matrices with question marks next to them. And now you need to turn that mess into a proper LaTeX document.

I had exactly this situation last winter. Forty-odd pages of handwritten notes from a research project, and a paper deadline looming. I knew I couldn’t retype everything — especially not the equations. There had to be a better way.

The copy-paste treadmill

The first tool I tried was Mathpix Snip. It’s well-known in academic circles for a reason: the OCR accuracy on mathematical notation is genuinely impressive. You screenshot a handwritten equation, and you get clean LaTeX back within seconds.

The problem wasn’t the accuracy. The problem was the workflow.

Every single equation involved:

  1. Take a screenshot or photo
  2. Switch to the OCR tool
  3. Wait for conversion
  4. Copy the LaTeX
  5. Switch back to my editor
  6. Paste and adjust

For a single equation, that’s fine. For forty pages? It’s death by a thousand context switches. By day two I was dreading opening my notes.

Finding TeX64

I stumbled across TeX64 while searching for “latex editor mac ocr built in” or something like that. It’s a macOS-only LaTeX editor — Apple Silicon and Intel both supported — and the key thing that caught my eye was that OCR is baked directly into the editor itself.

No separate app. No browser tab. No clipboard dance. You drop an image into the editor and the converted LaTeX appears in your document.

That sounded too good to be true, so I downloaded it (free tier, no account needed) and tried it on a page of my worst handwriting.

What it actually looks like

After installing MacTeX (TeX64 needs an existing TeX distribution — it’ll tell you if it can’t find one), I opened my project and dropped in a photo of my notebook.

A few seconds later, this appeared in my .tex file:

begin{align}
  frac{partial rho}{partial t} + nabla cdot (rho mathbf{u}) &= 0 \
  rho left( frac{partial mathbf{u}}{partial t} + mathbf{u} cdot nabla mathbf{u} right) &= -nabla p + mu nabla^2 mathbf{u}
end{align}

My handwritten Navier-Stokes equations, correctly transcribed, sitting right in my document in editable LaTeX. No switching apps. No clipboard. Just there.

I tested it on a few more pages. Printed math from a textbook screenshot? Near-perfect. My cleaner handwriting? Very good. My worst late-night scrawl? Mostly correct, with a few characters that needed manual fixing.

The pattern I noticed: TeX64 is excellent on math notation specifically. The structural stuff — frac, int, sum, matrix brackets — comes through cleanly. Individual symbols occasionally need correction, but the scaffolding is almost always right.

The workflow I landed on

Here’s what actually worked for my paper:

For each section of notes:

  1. Photograph the page (phone camera, decent lighting)
  2. Drop into TeX64 — OCR runs automatically
  3. Read through the output, fix any misread symbols
  4. Use Axiom (TeX64’s AI assistant) to clean up formatting

That last step was something I didn’t expect to use much but ended up relying on constantly. Axiom can read your compile logs — and since OCR output often has subtle structural errors (mismatched brackets, missing end tags), those errors showed up frequently.

Instead of debugging LaTeX by hand, I’d ask Axiom: “What’s wrong with this compile?” It would look at the actual log, identify the line, and show me a diff. One click to apply.

Example interaction:

Me: “Why is this not compiling?”
Axiom: “Line 312: begin{align*} is not closed. Here’s the fix:”
[shows diff]
[I click Apply]
Done.

That flow — photograph → OCR → Axiom cleanup → compile — got me through forty pages of notes in about two days instead of the week I’d budgeted for.

SyncTeX made verification fast

One thing that helped enormously with checking my work: SyncTeX is built in. You click anywhere in the PDF and jump to the corresponding source line. Click in the source and jump to the PDF location.

For verifying that a converted equation rendered correctly, this is incredibly useful. Instead of manually scrolling through both windows to find where something is, you click once and you’re there.

Honest limits

macOS only. This is a hard stop — TeX64 doesn’t run on Windows or Linux.

You still need MacTeX or TeX Live installed. TeX64 is the editor; it needs an external TeX distribution to compile. The setup takes maybe 20 minutes if you haven’t done it before (TeX64 will walk you through it if MacTeX is missing).

Handwritten text OCR isn’t as strong as math OCR. This tool is clearly optimized for mathematical notation. Regular handwritten prose came through with more errors, so I still typed out my non-equation text.

Free tier has limits. For a paper’s worth of OCR, you’ll likely bump into the usage cap and want the paid plan. That said, it’s worth trying on free to see if the accuracy meets your needs before committing.

The one thing that changed my workflow

Before TeX64, the OCR step and the editing step were two separate places in my brain. I had to context-switch between “get the LaTeX” mode and “write the paper” mode.

Having OCR inside the editor eliminated that. When I’m looking at a page of notes and need to pull an equation into my document, I don’t leave the app. I drop the image. It appears. I keep writing.

That’s it. That’s the whole difference. And for paper-writing momentum, it turned out to matter a lot.

If you’re on a Mac and you’re facing a stack of handwritten research notes: tex64.com. Worth a try.

Signals in React (V): Avoiding Tearing, Remount Leaks, and Transition Pitfalls

signals diagram

Goal of This Article

In this article, we will focus on three practical issues when integrating signals with React:

  • Understanding why tearing happens and how to guarantee tear-free subscriptions
  • Avoiding stale subscriptions and leftover computed nodes when components remount due to key changes
  • Handling consistency and timing coordination in Transition and Suspense scenarios

Data Flow and Responsibility Boundaries

The diagram below summarizes how data flows when signals are used inside a React environment.

signals diagram

We can divide responsibilities clearly:

  • Data side effects → our createEffect (business logic)
  • UI / DOM side effects → React’s useLayoutEffect / useEffect
  • Reading values → always through useSignalValue (useSyncExternalStore + peek() for tear-free behavior)

Tearing: Cause and Solution

We discussed tearing earlier, but let’s briefly recap.

Under React Concurrent Mode, React may read snapshots multiple times between render and commit.

If your snapshot is not controlled by React (for example, directly calling someSignal.get()), the UI may render with a snapshot that no longer matches the DOM after commit.

React’s Snapshot mechanism is a compromise to coordinate state updates and UI rendering.
It is not part of JavaScript itself, which is why some React developers develop incorrect mental models about JavaScript behavior.

Earlier chapters reviewed some JavaScript fundamentals to avoid this confusion. If you joined the series midway, it may be helpful to revisit those sections.

Two Common Incorrect Patterns

1. Reading .get() directly in a component

function Bad() {
  const v = someSignal.get(); // snapshot not controlled by React
  return <div>{v}</div>;
}

2. Manual subscription using useState + useEffect

function Bad() {
  const [v, setV] = useState(someSignal.peek());

  useEffect(() => {
    const stop = createEffect(() => {
      setV(someSignal.peek());
    }); // snapshot not re-read before commit

    return () => stop();
  }, []);

  return <div>{v}</div>;
}

Correct Approach: useSyncExternalStore

Always use useSyncExternalStore through our useSignalValue hook.

function Good() {
  const v = useSignalValue(someSignal); // tear-free
  return <div>{v}</div>;
}

Avoiding Unnecessary Re-renders: useSignalSelector

const user = signal({ id: 1, name: "Ada", age: 37 });

// Only re-render when name changes
function Name() {
  const name = useSignalSelector(user, u => u.name);
  return <h2>{name}</h2>;
}

Guidelines

  • Never call .get() during render
  • Always use useSignalValue or useSignalSelector
  • Do not implement subscriptions manually
  • useSyncExternalStore automatically re-reads the snapshot before commit and guarantees tear-free behavior

Remounting with key: Avoiding Stale Subscriptions and Memory Leaks

The Situation

When list keys change or routing switches occur, React will:

  1. Unmount the old tree
  2. Mount a new one

The Risk

If your derived values (computed / effect) have long lifetimes and live in module scope, their graph connections may still exist.

Problematic Example

export const expensive = computed(() => a.get() + b.get());

Even if some pages no longer need this value, upstream signals a and b may still retain subscriptions.

Solution

1. Bind to component lifecycle using useComputed

function Page() {
  const sum = useComputed(() => aSig.get() + bSig.get());
  const v = useSignalValue(sum);
  return <div>{v}</div>;
}

When the component unmounts, useComputed will automatically dispose the node.

2. Containerize with a Provider

const Ctx = createContext<{ sum: ReturnType<typeof computed> } | null>(null);

function StoreProvider({ children }: { children: React.ReactNode }) {
  const sum = useMemo(() => computed(() => aSig.get() + bSig.get()), []);

  useEffect(() => () => sum.dispose?.(), [sum]);

  return (
    <Ctx.Provider value={{ sum }}>
      {children}
    </Ctx.Provider>
  );
}

function Child() {
  const store = useContext(Ctx)!;
  const v = useSignalValue(store.sum);
  return <div>{v}</div>;
}

Practical Rule

Derived values under UI nodes → use useComputed
Global derived values → manage them with a Provider

Important Note About computed

computed is a node in the reactive graph (Observer + Trackable).

Dependencies are tracked only when signal.get() is called inside its callback.

If you pass React snapshots into useComputed, the computed node will not track any signals.

Incorrect Example

const count = useSignalValue(countSig);
const doubled = useComputed(() => count * 2); 
// computed runs once and never updates

Correct Example

const count = useSignalValue(countSig);
const doubled = useComputed(() => countSig.get() * 2);

Alternative: Pure React derivation

const count = useSignalValue(countSig);

const doubled = React.useMemo(() => count * 2, [count]);
// not part of the reactive graph

Transitions: Consistency and Timing Coordination

Important point:

startTransition only affects React’s setState priority.

It does not delay signal writes.

If some UI reads signals (useSignalValue) while other parts use setState + Transition, you may observe inconsistent UI timing.

Two Safe Strategies

Strategy 1: Always read external snapshots

All UI reads signals through useSignalValue.

For transition-like behavior, use useDeferredValue for display delay, rather than delaying data writes.

function SearchBox() {
  const q = useSignalValue(querySig);
  const deferredQ = useDeferredValue(q);

  return (
    <>
      <input
        value={q}
        onChange={e => querySig.set(e.target.value)}
      />
      <ExpensiveList query={deferredQ} />
    </>
  );
}

Strategy 2: Keep transitional values inside React state

A. React state as draft buffer

function Editor() {
  const committed = useSignalValue(titleSig);
  const [draft, setDraft] = useState(committed);

  useEffect(() => setDraft(committed), [committed]);

  const save = () => {
    startTransition(() => {
      titleSig.set(draft);
    });
  };

  return (
    <>
      <input value={draft} onChange={e => setDraft(e.target.value)} />
      <button onClick={save}>Save</button>
    </>
  );
}

Use this when you want to separate typing UI updates from heavy re-renders after commit.

B. Local signal state

function Editor() {
  const committed = useSignalValue(titleSig);
  const [draft, setDraft] = useSignalState(committed);

  useEffect(() => setDraft(committed), [committed]);

  const save = () => {
    titleSig.set(draft);
  };

  return (
    <>
      <input value={draft} onChange={e => setDraft(e.target.value)} />
      <button onClick={save}>Save</button>
    </>
  );
}

During editing, only the local signal updates, so downstream computations are unaffected.

C. Immediate writes but delayed UI

If you must update the signal on every keystroke, use useDeferredValue for expensive UI sections.

function Search() {
  const q = useSignalValue(querySig);
  const deferredQ = useDeferredValue(q);

  return (
    <>
      <input
        value={q}
        onChange={e => querySig.set(e.target.value)}
      />
      <ExpensiveList query={deferredQ} />
    </>
  );
}

The data stays real-time, while the UI update is deferred.

Common Misunderstandings

“If I use useSignalState, I no longer need startTransition?”

Usually yes.
Because useSignalState stores values locally and does not trigger global signal updates.

However, if committing still triggers heavy React state updates, those setState calls can still be wrapped in startTransition.

“If I wrap signal.set() inside startTransition, will it lower priority?”

No.
Transitions do not affect external stores.

Updates from external stores trigger re-renders through useSyncExternalStore, independent of startTransition.

Suspense: Consistency When Data Is Not Ready

Reality: signals do not automatically integrate with React Suspense.
Suspense only recognizes data sources that throw a Promise during render.

Strategy A: State-driven approach (simpler)

Data layer

const userId = signal(1);

const user = signal({
  status: "idle",
});
createEffect(() => {
  const id = userId.get();

  user.set({ status: "loading" });

  fetch(`/api/user/${id}`)
    .then(r => r.json())
    .then(d => user.set({ status: "ok", data: d }))
    .catch(e => user.set({ status: "error", err: e }));
});

UI

function UserPanel() {
  const u = useSignalValue(user);

  if (u.status === "loading") return <Spinner />;
  if (u.status === "error") return <ErrorView err={u.err} />;

  return <Profile data={u.data} />;
}

Strategy B: Convert into a Suspense resource

export function toResource(src) {
  let thrown = null;

  return {
    read() {
      const s = src.peek();

      if (s.status === "ok") return s.data;
      if (s.status === "error") throw s.err;

      if (!thrown) thrown = new Promise(() => {});
      throw thrown;
    }
  };
}
function UserPanel() {
  const resource = React.useMemo(() => toResource(user), []);

  const data = resource.read();

  return <Profile data={data} />;
}
<React.Suspense fallback={<Spinner />}>
  <UserPanel />
</React.Suspense>

When to choose which?

If your project does not heavily rely on Suspense → Strategy A is simpler

If you already have Suspense infrastructure → Strategy B works with an adapter

Just remember to provide a real pending Promise.

Conclusion

At this point, you can use your signals in React 18+ with:

  • Stable tear-free subscriptions
  • Minimal unnecessary re-renders
  • Clear responsibility boundaries between React and signals

In the next article, we will look at common anti-patterns and how to fix them.