<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tombri Bowei</title>
    <description>The latest articles on DEV Community by Tombri Bowei (@_boweii).</description>
    <link>https://dev.to/_boweii</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1745361%2Fa5ccba4b-d4ad-4f12-8265-dcd18c3775cd.jpg</url>
      <title>DEV Community: Tombri Bowei</title>
      <link>https://dev.to/_boweii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_boweii"/>
    <language>en</language>
    <item>
      <title>The Emotional Lifecycle of Every Side Project (A Map)</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Fri, 01 May 2026 00:07:22 +0000</pubDate>
      <link>https://dev.to/_boweii/the-emotional-lifecycle-of-every-side-project-a-map-3gbc</link>
      <guid>https://dev.to/_boweii/the-emotional-lifecycle-of-every-side-project-a-map-3gbc</guid>
      <description>&lt;p&gt;&lt;em&gt;You've been here before. You just didn't have a map.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You've done this enough times to know the pattern exists.&lt;/p&gt;

&lt;p&gt;The surge of excitement. The quiet middle where everything gets hard. The weird guilt of abandoning something you cared about. The random Tuesday six months later when you open it again for no reason.&lt;/p&gt;

&lt;p&gt;Every side project follows the same emotional arc. Not the same technical arc — the emotional one. The feelings arrive in the same order, at roughly the same intervals, with the same intensity, regardless of what you're building or how experienced you are.&lt;/p&gt;

&lt;p&gt;The developers who finish things aren't the ones who escape this cycle. They're the ones who've mapped it. Who know which phase they're in. Who recognise the feeling that's trying to make them quit and call it by its name instead of listening to it.&lt;/p&gt;

&lt;p&gt;This is that map.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: The Spark ⚡
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Electricity. Clarity. The specific euphoria of an idea that feels genuinely new.&lt;/p&gt;

&lt;p&gt;You're in the shower. On a walk. Half asleep. And then — there it is. A problem you've had that nobody's solved properly. A tool you wish existed. A thing you could build that would be genuinely useful, genuinely interesting, genuinely yours.&lt;/p&gt;

&lt;p&gt;The idea arrives fully formed in a way that never survives contact with reality but feels completely real in the moment. You can see the landing page. You can see the UI. You can see people using it. You open Notes and type three paragraphs in a row without stopping.&lt;/p&gt;

&lt;p&gt;For a few hours — sometimes days — the idea is perfect. Untouched by execution. Unspoiled by the actual work of building it.&lt;/p&gt;

&lt;p&gt;This phase is intoxicating and completely unreliable as a signal of anything. Every project that got abandoned started here. Every project that changed someone's life also started here.&lt;/p&gt;

&lt;p&gt;The spark doesn't tell you which one this is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;This one is different. This one is the one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; Maybe. Doesn't matter yet. Write it down and start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: The Setup High 🛠️
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Momentum without resistance. Pure possibility.&lt;/p&gt;

&lt;p&gt;You create the repo. You scaffold the project. You pick the stack — and honestly this decision takes longer than it should because you're also half-dreaming about what this could become.&lt;/p&gt;

&lt;p&gt;Everything in this phase is easy because nothing is real yet. You're not solving the hard problem. You're arranging the tools you'll use to solve it. And arranging tools feels like progress because technically it is.&lt;/p&gt;

&lt;p&gt;The README gets written. The folder structure gets decided. The color palette gets chosen. You might even make a Figma file. You're building the scaffolding around a building that doesn't exist yet, and it feels fantastic.&lt;/p&gt;

&lt;p&gt;This phase can last hours or days depending on how good you are at starting and how afraid you are of the actual work. Some people have perfected the Setup High into an art form — they have thirty perfectly scaffolded repositories and zero finished products.&lt;/p&gt;

&lt;p&gt;The Setup High is the side project equivalent of buying running shoes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;Starting is the hardest part.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; Starting is the easiest part. By a significant margin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: Early Momentum 🚀
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Confirmation. You're doing it. It's actually happening.&lt;/p&gt;

&lt;p&gt;The first real features exist. The thing works, roughly. You can open it in a browser and see something that resembles the idea in your head. The gap between vision and reality is still enormous but it's no longer infinite.&lt;/p&gt;

&lt;p&gt;You're coding fast. Things are clicking. Each session ends with something visibly different from where it started. You tell someone about it — a friend, a partner, a Discord — and saying it out loud makes it feel more real.&lt;/p&gt;

&lt;p&gt;This is the phase where the GitHub contribution graph starts looking good. Where the commits are daily. Where working on the project feels less like effort and more like play.&lt;/p&gt;

&lt;p&gt;Sleep suffers. That's how you know it's real.&lt;/p&gt;

&lt;p&gt;This phase is deceptive because it creates a false baseline. The pace feels sustainable. It isn't. The easy parts of any project get built first, by definition. The rate of visible progress will slow. When it does, your brain will interpret the slowdown as a sign that something is wrong.&lt;/p&gt;

&lt;p&gt;Nothing is wrong. The easy parts just ran out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;I could ship this in two weeks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; Multiply that estimate by four. Minimum.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4: The First Wall 🧱
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Friction. Sudden, unexpected, demoralising friction.&lt;/p&gt;

&lt;p&gt;You hit the thing you'd been quietly avoiding thinking about. The hard technical problem at the centre of the project. The feature that doesn't have a clean solution. The architecture decision you deferred because you didn't want to deal with it yet.&lt;/p&gt;

&lt;p&gt;Progress slows dramatically. Sessions that used to produce visible results now produce invisible ones — refactoring, thinking, reading docs, abandoning approaches, trying again. The gap between where you are and where you want to be stops shrinking.&lt;/p&gt;

&lt;p&gt;You spend an entire evening on something that should take an hour. You close the laptop feeling like you went backwards. You open it the next day and spend twenty minutes staring before writing a single line.&lt;/p&gt;

&lt;p&gt;This is where most projects die. Not because the problem is unsolvable. Because the slowdown feels like failure. Because the feeling of momentum was so good and this feeling is so bad and your brain presents abandonment as relief.&lt;/p&gt;

&lt;p&gt;The First Wall is not a sign that the project is flawed.&lt;/p&gt;

&lt;p&gt;It's a sign that the project is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;Maybe this idea wasn't as good as I thought.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; The idea is exactly as good as you thought. You just reached the part that requires actual work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 5: The Trough 🕳️
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Contempt. For the project, the code, the original idea, yourself.&lt;/p&gt;

&lt;p&gt;You open the codebase and feel nothing but irritation. The early decisions you made look naive. The code that felt clean two weeks ago looks like a mess. The original concept — the thing that felt so obvious and right — suddenly seems embarrassing.&lt;/p&gt;

&lt;p&gt;You look at other people's projects. Everything they ship looks better than what you're building. You go on Twitter and see someone announce a product that's adjacent to your idea and it's already further along than you are and better designed and you feel a specific cocktail of envy and deflation that is deeply unpleasant.&lt;/p&gt;

&lt;p&gt;The Trough is where you seriously consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scrapping everything and starting over with a better architecture&lt;/li&gt;
&lt;li&gt;Pivoting the idea entirely&lt;/li&gt;
&lt;li&gt;Abandoning it and pretending it never happened&lt;/li&gt;
&lt;li&gt;Finishing it really quickly just to be done with it even though "done" would mean shipping something you're not proud of&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are the right move. All of them feel like the right move.&lt;/p&gt;

&lt;p&gt;The Trough is a phase. Not a verdict.&lt;/p&gt;

&lt;p&gt;Every single project you've ever admired — every product you've used and loved, every open source tool you've depended on, every portfolio piece that impressed you — passed through a Trough. The difference between the ones that made it out and the ones that didn't is almost never quality. It's almost always just someone deciding to keep going when keeping going felt pointless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;This is when I should quit.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; This is exactly when you shouldn't. The Trough means you're close to something real.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 6: The Quiet Rebuild 🔧
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Resignation, then something quieter. Steadier.&lt;/p&gt;

&lt;p&gt;You stop expecting sessions to feel exciting. You start treating the project like a job — not in a bad way, in a professional way. You show up. You do the work. You close the laptop without needing it to have felt good.&lt;/p&gt;

&lt;p&gt;Something shifts in this phase. The emotional charge fades and in its place comes something more durable: commitment. You're not building because it's fun anymore. You're building because you said you would. Because the idea still matters even when the feeling is gone. Because finishing is a skill and you're practicing it.&lt;/p&gt;

&lt;p&gt;The code gets cleaner in this phase, not because you have more energy but because you have more clarity. The decisions you agonised over in the Trough resolve themselves quietly. You know the codebase well enough now to move through it with confidence even without enthusiasm.&lt;/p&gt;

&lt;p&gt;This is the phase that separates the developers who ship from the ones who don't. Because nothing about this phase feels like a highlight. There's no story to tell. No dramatic breakthrough. Just sessions of steady, unremarkable work that collectively build the thing.&lt;/p&gt;

&lt;p&gt;Most of the best software in the world was built mostly in this phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;If I'm not excited, something is wrong.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; Excitement is a spark. Commitment is a furnace. You need both but only one of them keeps burning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 7: The Emergence 🌅
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Surprise. The thing is actually becoming what you imagined.&lt;/p&gt;

&lt;p&gt;You can't pinpoint when it happened. One session you were grinding through the Quiet Rebuild and then suddenly — the pieces are connecting. The features that existed in isolation are working together. You load it in the browser and for the first time it looks like software. Real software. The kind someone else might actually use.&lt;/p&gt;

&lt;p&gt;The gap between what you imagined in Phase 1 and what exists now is still real. But for the first time it feels closeable. The end is visible from where you're standing.&lt;/p&gt;

&lt;p&gt;Excitement comes back, quieter than before, more earned. You find yourself thinking about it during the day. Making notes. Seeing things you want to fix, things you want to add. The project has gravity again.&lt;/p&gt;

&lt;p&gt;This phase is also when you start getting scared in a new way. Before you were scared it would never be good enough. Now you're scared of what happens when you actually share it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;I should keep polishing. It's not ready.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; It will never feel ready. That's not what ready means.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 8: The Pre-Ship Spiral 🌀
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Productive procrastination disguised as perfectionism.&lt;/p&gt;

&lt;p&gt;You're so close. Which means every remaining imperfection is now visible and intolerable. The mobile layout is slightly off. The loading state isn't quite right. The copy on the landing page doesn't feel tight enough. The README needs one more pass.&lt;/p&gt;

&lt;p&gt;You tell yourself you're being professional. Thorough. That you have high standards. All of that is true and also completely irrelevant because what's actually happening is fear.&lt;/p&gt;

&lt;p&gt;Fear that it won't land. That nobody will care. That the people who do see it will see the cracks you can see. That shipping means accountability and not shipping means you can still believe it could be great.&lt;/p&gt;

&lt;p&gt;The Pre-Ship Spiral can last days. For some projects it lasts long enough that the project never ships. The developer keeps polishing a thing in private indefinitely, always one small fix away from ready, until eventually the moment passes and the project joins the graveyard.&lt;/p&gt;

&lt;p&gt;The only move is to pick a date and treat it like a non-negotiable.&lt;/p&gt;

&lt;p&gt;Not when it's perfect. When it's good enough to be useful. Those are different things and conflating them is the thing that kills more good projects than any technical problem ever could.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;Just one more thing and then it'll be ready.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; Ship it. Now. Today. The version that exists beats the perfect version that doesn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 9: The Deploy 🚢
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; A strange calm. Then nothing. Then something.&lt;/p&gt;

&lt;p&gt;You push to production. You share the link somewhere — Twitter, dev.to, a Discord, a subreddit, an email to three people. You close the tab immediately because you can't watch.&lt;/p&gt;

&lt;p&gt;The next twenty minutes are a specific kind of suspended animation. You refresh things you've already refreshed. You make tea you don't drink. You check your phone for no reason.&lt;/p&gt;

&lt;p&gt;And then someone sees it. Someone clicks it. Someone says something — anything — and the thing that was only in your head and then only on your laptop and then only on a domain nobody knew existed is suddenly, unmistakably, real.&lt;/p&gt;

&lt;p&gt;The feeling isn't always joy. Sometimes it's relief. Sometimes it's anticlimactic. Sometimes you feel less than you expected and that's confusing.&lt;/p&gt;

&lt;p&gt;But underneath all of it, quiet and steady, is something that doesn't go away:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built this. It exists. Nobody can unship it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lie this phase tells you:&lt;/strong&gt; &lt;em&gt;The response will tell me if it was worth it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The truth:&lt;/strong&gt; It was already worth it. The response is just data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 10: The Aftermath 🌊
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it feels like:&lt;/strong&gt; Everything, in waves.&lt;/p&gt;

&lt;p&gt;Pride. Vulnerability. The sudden, sharp vision of every flaw now that other people can see it. Unexpected gratitude when a stranger says it helped them. The strange loneliness of a project that's out in the world and no longer entirely yours.&lt;/p&gt;

&lt;p&gt;And eventually, quietly, an itch.&lt;/p&gt;

&lt;p&gt;A thing you'd do differently. A feature that would make it better. A new problem the project revealed that you want to go solve.&lt;/p&gt;

&lt;p&gt;The cycle doesn't end. It just starts again, smaller, more confident, with the knowledge that you've been through this before and you know where it goes.&lt;/p&gt;

&lt;p&gt;That knowledge doesn't make it easier.&lt;/p&gt;

&lt;p&gt;But it makes it survivable. And survivable, for a builder, is enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Map Is Not the Territory
&lt;/h2&gt;

&lt;p&gt;Reading this won't stop you from feeling every phase as if it's happening for the first time. Emotions don't care that you've mapped them. The Trough will still feel like a verdict. The Pre-Ship Spiral will still feel like thoroughness.&lt;/p&gt;

&lt;p&gt;But somewhere in the middle of Phase 5, when your brain is presenting quitting as the only reasonable option — maybe you'll remember you've seen this before. That you know what phase this is. That the map says you're closer than you feel.&lt;/p&gt;

&lt;p&gt;That's all a map is for.&lt;/p&gt;

&lt;p&gt;Not to remove the terrain. Just to remind you that others have crossed it.&lt;/p&gt;

&lt;p&gt;And that the other side exists.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Which phase are you in right now? Drop it in the comments — no context needed, just the phase number. Let's see where everyone is on the map. 👇&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Follow for more honest writing about the craft and psychology of building things.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>career</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Most Important Announcement at Google Cloud NEXT '26 That Nobody Is Talking About</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Mon, 27 Apr 2026 23:57:04 +0000</pubDate>
      <link>https://dev.to/_boweii/the-most-important-announcement-at-google-cloud-next-26-that-nobody-is-talking-about-32cj</link>
      <guid>https://dev.to/_boweii/the-most-important-announcement-at-google-cloud-next-26-that-nobody-is-talking-about-32cj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-cloud-next-2026-04-22"&gt;Google Cloud NEXT Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Everyone came out of Google Cloud NEXT '26 talking about the same things.&lt;/p&gt;

&lt;p&gt;The Gemini Enterprise Agent Platform. The 8th-generation TPUs. The Apple partnership. The $32 billion Wiz acquisition is baked into their security stack. All legitimate, all impressive, all getting the clicks they deserve.&lt;/p&gt;

&lt;p&gt;But buried 35 minutes deep inside the Developer Keynote — sandwiched between a Las Vegas marathon simulation and a joke about running being awful — was a two-minute demo of something called &lt;strong&gt;'A2UI'&lt;/strong&gt; that I haven't seen a single think piece about.&lt;/p&gt;

&lt;p&gt;That's a mistake. Because A2UI is the announcement that will actually change what it means to be a frontend developer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Even Is A2UI?
&lt;/h2&gt;

&lt;p&gt;Here's the demo setup from the keynote: the marathon planning system had an evaluator agent that scored proposed race routes. The problem was, how do you show those evaluation results to a user without hardcoding a dashboard?&lt;/p&gt;

&lt;p&gt;The answer the team gave was A2UI. The agent didn't return the text. It didn't run code. It returned a &lt;strong&gt;declarative JSON description&lt;/strong&gt; of a UI — cards, maps, scores, and components — and the frontend rendered it natively using its own existing widgets. The agent &lt;em&gt;designed&lt;/em&gt; the interface. The frontend just executed it.&lt;/p&gt;

&lt;p&gt;That's the shift. And it sounds subtle until you sit with it.&lt;/p&gt;

&lt;p&gt;A2UI (Agent-to-User Interface) is an open standard — Apache 2.0, on GitHub, with contributions from Google and CopilotKit — that lets AI agents "speak UI". Instead of responding with a wall of text, an agent sends a structured JSON payload describing what the interface should look like. The client application maintains a trusted catalogue of pre-approved components (a &lt;code&gt;Card&lt;/code&gt;, a &lt;code&gt;Button&lt;/code&gt;, a &lt;code&gt;DatePicker&lt;/code&gt;, a ``), and the agent can only compose from that catalogue. It cannot inject code. It cannot execute anything arbitrary. It declares &lt;em&gt;intent&lt;/em&gt;, and the frontend renders it using its own native widgets.&lt;/p&gt;

&lt;p&gt;The result: one agent response that renders natively across Angular, Flutter, React, and mobile — without a single extra line of UI code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Is Bigger Than It Looks
&lt;/h2&gt;

&lt;p&gt;Let me frame the problem A2UI is actually solving, because the keynote moved too fast to do it justice.&lt;/p&gt;

&lt;p&gt;Right now, when you build an AI agent, you face a brutal choice. Either you give it a fixed UI — hardcode the dashboard, predecide every screen, ship a design system and pray the agent's outputs fit the layout you chose — or you let it generate code on the fly, which is a security nightmare and a maintenance hell you don't want to have.&lt;/p&gt;

&lt;p&gt;Neither option is good. Fixed UIs make your agent feel dumb ("why can't it just show me a chart?"). Generated code makes your security team quit.&lt;/p&gt;

&lt;p&gt;A2UI is the third option nobody realised they were waiting for: &lt;strong&gt;declarative, sandboxed, generative UI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The agent says: &lt;em&gt;"I want to show the user a card with a map, two metric tiles, and a confirm button."&lt;/em&gt; The client says, "I have those components. Here they are, styled to my design system, following my security policies."* The user sees a beautiful, contextually appropriate interface that didn't exist in code five seconds ago.&lt;/p&gt;

&lt;p&gt;What makes this genuinely new — and genuinely safe — is that the agent &lt;em&gt;cannot&lt;/em&gt; reach outside the catalogue. There's no UI injection vector. There's no arbitrary code execution. The trust boundary is clean. The developer stays in control of what components exist. The agent only gets to compose.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Keynote Demo Undersold It
&lt;/h2&gt;

&lt;p&gt;In the marathon simulation demo, the presenters used A2UI to render a route evaluation card. Nice. Functional. Easy to gloss over.&lt;/p&gt;

&lt;p&gt;But watch what happens when you extend that idea:&lt;/p&gt;

&lt;p&gt;Imagine a customer support agent. A user files a complaint. Instead of the agent dumping a paragraph of text, it generates a structured claim form — pre-filled with the details it already knows, with a date picker for the incident, a dropdown for severity, and a submit button wired to your existing backend. The agent composed that form. Your design system rendered it. The user never typed more than they had to.&lt;/p&gt;

&lt;p&gt;Imagine a developer tools agent. A user asks, "What's wrong with my deployment?" Instead of a wall of logs, the agent returns a tabbed interface: one tab for errors, one for warnings, and one for a recommended fix with a one-click apply button. The agent decided that three tabs were the right answer to &lt;em&gt;this&lt;/em&gt; question. Tomorrow it might decide a timeline view is better. You didn't write either layout.&lt;/p&gt;

&lt;p&gt;This is what the keynote called moving "beyond walls and walls of text". That's underselling it. What A2UI actually moves us beyond is the assumption that the UI is a static thing that developers design in advance and agents fill in. The interface itself becomes a runtime decision. The agent decides what shape the information should take.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where It Stands Right Now
&lt;/h2&gt;

&lt;p&gt;A2UI is currently at &lt;strong&gt;v0.9 (Public Preview)&lt;/strong&gt;, just updated two weeks ago. It's functional, it's on GitHub, and it already supports Angular, Flutter, Lit, and React renderers. The v0.9 release added a shared web-core library, an official React renderer, client-defined functions for validation, and a new Agent SDK that handles streaming, version negotiation, and incremental parsing — so the UI builds in real-time as the agent generates it, rather than waiting for a full JSON block to arrive.&lt;/p&gt;

&lt;p&gt;It's not 1.0 yet. The spec is still evolving. Some renderer integrations are more mature than others. But the core idea is stable, the open-source community is active (CopilotKit has already shipped an A2UI starter template and composer tool), and Google is clearly building it into the broader ADK/A2A/Gemini stack as a first-class citizen.&lt;/p&gt;

&lt;p&gt;The trajectory is obvious: every agent that ships on Google's platform will eventually be able to speak A2UI. Every frontend that integrates the renderer gets dynamic, agent-composed interfaces for free.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Frontend Developers
&lt;/h2&gt;

&lt;p&gt;I want to be precise here, because I'm not making a doom-and-gloom argument.&lt;/p&gt;

&lt;p&gt;A2UI doesn't make frontend developers obsolete. It changes what they build.&lt;/p&gt;

&lt;p&gt;Instead of building screens, you'll build &lt;strong&gt;component catalogues&lt;/strong&gt; — the vocabulary the agent is allowed to speak. Instead of designing flows, you'll define the grammar. Instead of wiring up every state transition, you'll write the rendering logic once and let the agent decide when and how to assemble it.&lt;/p&gt;

&lt;p&gt;That's actually a more interesting job. You're no longer implementing a specific user journey. You're building a system that can express any user journey the agent determines is appropriate.&lt;/p&gt;

&lt;p&gt;But it does mean the instinct to hardcode — to say "this agent always shows a table, this one always shows a form" — will increasingly be the wrong instinct. The instinct you want is "What components does this agent need access to, and what should it never be able to render?"&lt;/p&gt;

&lt;p&gt;Frontend developers who think in those terms will be indispensable in the agentic era. Those who keep thinking in terms of fixed layouts will find themselves constantly rebuilding UIs to accommodate agent outputs that don't fit the box they designed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Underrated Bet
&lt;/h2&gt;

&lt;p&gt;Google announced a lot at NEXT '26. The TPUs are extraordinary. The agent platform is genuinely ambitious. The Apple partnership is a strategic earthquake.&lt;/p&gt;

&lt;p&gt;But A2UI is the announcement with the longest tail.&lt;/p&gt;

&lt;p&gt;Every other announcement requires infrastructure, budget, and migration planning. A2UI is a protocol. It's open source. It runs today on the frameworks developers already use. It's the kind of thing that quietly becomes load-bearing in the ecosystem before anyone agrees it's important.&lt;/p&gt;

&lt;p&gt;It got two minutes on stage at the developer keynote. It deserved twenty.&lt;/p&gt;

&lt;p&gt;If you build agents, go read the &lt;a href="https://a2ui.org" rel="noopener noreferrer"&gt;A2UI spec&lt;/a&gt;. If you build frontends that agents talk to, go integrate the renderer. If you're designing the next generation of AI-powered products, go think hard about what a component catalogue means for your design system.&lt;/p&gt;

&lt;p&gt;The marathon demo was fun. But A2UI is what I'll still be thinking about in six months.&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>cloudnextchallenge</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>What It Actually Feels Like to Build Something You're Proud Of</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Mon, 27 Apr 2026 21:45:52 +0000</pubDate>
      <link>https://dev.to/_boweii/what-it-actually-feels-like-to-build-something-youre-proud-of-35mi</link>
      <guid>https://dev.to/_boweii/what-it-actually-feels-like-to-build-something-youre-proud-of-35mi</guid>
      <description>&lt;p&gt;&lt;em&gt;Nobody talks about the emotional side of shipping. Let's fix that.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;There's a specific kind of silence that happens right after you deploy something real.&lt;/p&gt;

&lt;p&gt;Not the silence of a bug you haven't found yet. Not the silence of waiting for the CI pipeline to clear. A different kind. The kind where you close your laptop, lean back, and just... sit with it.&lt;/p&gt;

&lt;p&gt;If you've felt it, you know exactly what I mean.&lt;/p&gt;

&lt;p&gt;If you haven't felt it yet, this article is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  It Doesn't Start With Excitement. It Starts With Dread.
&lt;/h2&gt;

&lt;p&gt;Here's what nobody puts in their "I shipped a side project!" LinkedIn post:&lt;/p&gt;

&lt;p&gt;The beginning is awful.&lt;/p&gt;

&lt;p&gt;You open a blank &lt;code&gt;index.html&lt;/code&gt; or a fresh one, and suddenly the weight of the idea feels crushing. You had a vision in your head. Fully formed. Beautiful. And then you look at a white screen with a blinking cursor, and the gap between &lt;em&gt;what you imagined&lt;/em&gt; and &lt;em&gt;what currently exists&lt;/em&gt; feels insurmountable.&lt;/p&gt;

&lt;p&gt;This is the part people skip when they talk about building things. They show you the polished Figma mockup, the finished landing page, and the GitHub repo with 400 stars. They don't show you the three hours they spent just trying to decide on a folder structure.&lt;/p&gt;

&lt;p&gt;The dread is real. The friction is real. And it's not a sign that you're doing it wrong — it's the price of entry for building something that actually matters to you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The gap between your taste and your current ability is not a flaw in you. It's evidence that your taste is working.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a real thing. The people who never feel that gap are the people who don't have high standards for their own work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Middle: Where Most Things Go to Die
&lt;/h2&gt;

&lt;p&gt;At some point in every project — usually around 40% of the way through — you will hate it.&lt;/p&gt;

&lt;p&gt;Not mildly dislike it. Hate it. You'll look at what you've built and feel nothing but contempt. The colours feel wrong. The code feels messy. The whole concept suddenly seems embarrassing. You'll open Twitter, see someone ship something that looks better than yours, and quietly close your laptop.&lt;/p&gt;

&lt;p&gt;This is the trough. Every creative person has a name for it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writers call it the &lt;em&gt;'saggy middle'.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Musicians call it &lt;em&gt;'demo-itis'.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Filmmakers call it &lt;em&gt;the 'rough cut that makes the director cry'.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For developers, it's the moment you seriously consider scrapping everything and starting over. Or worse — just abandoning the project entirely and telling yourself you'll "come back to it later".&lt;/p&gt;

&lt;p&gt;You won't come back to it. We both know that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The only way out of the trough is through it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not around it. Not by pivoting to a new idea. Not by starting fresh. Through. Keep pushing. Ship &lt;em&gt;something&lt;/em&gt;. Iterate. The feeling on the other side is worth it in a way that's almost impossible to describe until you've experienced it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment the Thing Comes Alive
&lt;/h2&gt;

&lt;p&gt;And then—if you survive the trough—something shifts.&lt;/p&gt;

&lt;p&gt;It's usually small. An animation finally feels right. Two components click together in a way you didn't plan. You load it in the browser, and for the first time it looks like the thing you imagined at the beginning. Not exactly. Better.&lt;/p&gt;

&lt;p&gt;This is the moment developers don't talk about enough. When the project stops being a problem you're solving and starts being a &lt;em&gt;thing that exists in the world.&lt;/em&gt; When you catch yourself using your own app and forgetting that you built it.&lt;/p&gt;

&lt;p&gt;I've spoken to dozens of developers about this moment, and the words they reach for are almost always the same:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"It felt real."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Not finished. Real. There's a difference. Finished is when all the tasks are done. Real is when it stops feeling like a side project and starts feeling like software.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Shipping Actually Feels Like
&lt;/h2&gt;

&lt;p&gt;Here's the emotional sequence — as honestly as I can write it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T-minus 1 hour:&lt;/strong&gt; Quiet panic. You're finding small things to fix that don't need fixing. You're re-reading your README for the fifth time. You're refreshing your deployment preview.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T-minus 10 minutes:&lt;/strong&gt; Resignation. You've accepted that it's not perfect. That there are still edge cases you haven't handled. That the mobile nav is slightly off on the iPhone SE. You hit deploy anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T-zero:&lt;/strong&gt; A strange calm. The kind that comes after a decision is made and can't be unmade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T-plus 5 minutes:&lt;/strong&gt; You share it somewhere. A tweet. A Discord. Submitting to dev.to. And then you close the tab immediately because you can't watch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T-plus 20 minutes:&lt;/strong&gt; You open the tab. Someone liked it. Someone actually looked at the thing you made. And something in your chest does a thing that's hard to describe — it's not quite pride, not quite relief. It's closer to &lt;em&gt;vindication&lt;/em&gt;. Proof that the idea wasn't just in your head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;T-plus a few days:&lt;/strong&gt; You look at it again, and you can see every flaw clearly. But you don't feel ashamed of them. You feel like a person who made a real thing and learned real things in the process of making it.&lt;/p&gt;

&lt;p&gt;That's it. That's what shipping feels like.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Thing About Being Proud of Your Work
&lt;/h2&gt;

&lt;p&gt;Pride is a complicated word in developer culture. We're trained to be humble. To say "it's just a side project" is to minimise it. To pre-emptively apologise for the code quality before anyone even looks at it.&lt;/p&gt;

&lt;p&gt;But there's a version of pride that has nothing to do with arrogance. It's the quiet satisfaction of knowing that something exists because &lt;em&gt;you&lt;/em&gt; made it exist. That a year ago it was nothing, and now it is something. That if you didn't build it, it simply wouldn't be there.&lt;/p&gt;

&lt;p&gt;That's not arrogance. That's a craft.&lt;/p&gt;

&lt;p&gt;The developers I respect most aren't the ones with the cleanest code or the most GitHub stars. They're the ones who finish things. Who ships things? Who look at what they've built and – even knowing all the shortcuts they took and all the technical debt they accumulated – feel something?&lt;/p&gt;

&lt;p&gt;Because code without feeling is just syntax. It's the feeling that makes it worth building.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I've Learned From Building Things I'm Proud Of
&lt;/h2&gt;

&lt;p&gt;A few things that are actually true, earned from time in the trough:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constraints make you more creative, not less.&lt;/strong&gt; The projects I'm most proud of weren't the ones with unlimited scope. They were the ones where I had a weekend, a weird idea, and no time to second-guess myself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The version you ship will always feel unfinished.&lt;/strong&gt; Ship it anyway. "Done and public" beats "perfect and private" every single time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other people's opinions of your work are data, not verdicts.&lt;/strong&gt; When someone loves what you built, it tells you something useful. When someone doesn't, it also tells you something useful. Neither defines whether you should keep building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pride compounds.&lt;/strong&gt; The first thing you ship feels terrifying. The second feels hard. By the tenth, shipping is just something you do. The fear never fully goes away — but it gets smaller relative to the satisfaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building something you're proud of changes how you see yourself.&lt;/strong&gt; Not in a dramatic way. In a quiet way. You start to think of yourself as someone who makes things. And that identity — builder, creator, maker — is one of the most useful identities a developer can carry.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Question for You
&lt;/h2&gt;

&lt;p&gt;What's the thing you've built that you're most proud of? Not the most technically impressive. Not the one with the most stars or the most users.&lt;/p&gt;

&lt;p&gt;The one that made you feel something when you shipped it.&lt;/p&gt;

&lt;p&gt;Drop it in the comments. I want to see what you've made.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this resonated with you, follow along — I write about the craft and psychology of building things as a developer, not just the technical how-tos. Real talk, no fluff.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>career</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Honest Windows Guide to OpenClaw: What I Learned After a Full Day of Errors</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sun, 26 Apr 2026 21:25:25 +0000</pubDate>
      <link>https://dev.to/_boweii/the-honest-windows-guide-to-openclaw-what-i-learned-after-a-full-day-of-errors-5cd8</link>
      <guid>https://dev.to/_boweii/the-honest-windows-guide-to-openclaw-what-i-learned-after-a-full-day-of-errors-5cd8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm going to be straight with you: I did not finish my OpenClaw project today.&lt;/p&gt;

&lt;p&gt;I set out to build ClearView — an accessibility skill that would let blind and low-vision users describe their screen, read documents, and navigate their computer just by messaging a Telegram bot. Great idea. Real use case. I was excited.&lt;/p&gt;

&lt;p&gt;What I actually spent my day doing was fighting Windows.&lt;/p&gt;

&lt;p&gt;And I'm writing this post because when I was googling every error I hit, I found nothing useful. Every OpenClaw tutorial assumes you're on a Mac. Every setup guide breezes past the "install it and run it" step like it's nothing. For Windows users — especially those using nvm4w — it is very much not nothing.&lt;/p&gt;

&lt;p&gt;So here's the guide I wish existed. The honest one. With the real errors and the real fixes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Was Building
&lt;/h2&gt;

&lt;p&gt;The idea was simple: a skill for blind and low-vision users that lets them message their bot and get back a plain-English description of whatever is on their screen, or have documents and images read aloud to them. No new apps to learn. No complex interface. Just Telegram, OpenClaw, and Claude's vision capability.&lt;/p&gt;

&lt;p&gt;It's a real problem worth solving. Screen readers are expensive, rigid, and require training. Claude can describe a screenshot in plain language instantly. The gap between those two things is exactly what OpenClaw is built to bridge.&lt;/p&gt;

&lt;p&gt;I didn't finish the skill. But I did get OpenClaw running on Windows — and that alone took most of the day. Here's everything I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup: What the Docs Say vs What Actually Happens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Node version matters — but so does &lt;em&gt;how&lt;/em&gt; you installed it
&lt;/h3&gt;

&lt;p&gt;The docs say you need Node 22 or 24. Fine. What they don't tell you is that if you installed Node via &lt;strong&gt;nvm4w&lt;/strong&gt; (Node Version Manager for Windows), OpenClaw may not find it correctly.&lt;/p&gt;

&lt;p&gt;When I first tried to launch the gateway, I got the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Error: failed to launch TUI: spawn C:\Program Files\nodejs\node.exe ENOENT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The problem: nvm4w stores Node at &lt;code&gt;C:\nvm4w\nodejs\node.exe&lt;/code&gt;, not the default path OpenClaw expects. The fix is to &lt;strong&gt;run Command Prompt as Administrator&lt;/strong&gt; — this ensures the path resolves correctly. Every single time. Don't skip this step.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Telegram plugin EPERM error
&lt;/h3&gt;

&lt;p&gt;When I first ran the gateway, I saw this buried in the logs:&lt;br&gt;
[plugins] telegram failed during register: Error: EPERM: operation not permitted,&lt;br&gt;
rename '....plugin-telegram-lubdRt\plugin' -&amp;gt; '...\telegram'&lt;/p&gt;

&lt;p&gt;This is Windows blocking OpenClaw from renaming a temp folder during plugin setup. The fix is the same: &lt;strong&gt;run as Administrator&lt;/strong&gt;. Once I did that, the error disappeared and Telegram loaded cleanly.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Setting your Telegram token — the right command
&lt;/h3&gt;

&lt;p&gt;During onboarding I tried:&lt;br&gt;
openclaw config set telegram.token "YOUR_TOKEN"&lt;/p&gt;

&lt;p&gt;This gives you:&lt;br&gt;
Error: Config validation failed: : Unrecognized key: "telegram"&lt;/p&gt;

&lt;p&gt;The correct command is:&lt;br&gt;
openclaw channels add --channel telegram --token "YOUR_TOKEN_HERE"&lt;/p&gt;

&lt;p&gt;That's it. One line. Not in the getting started docs anywhere that I could find.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Setting your Claude API key
&lt;/h3&gt;

&lt;p&gt;Similarly, trying to set the Anthropic key through config commands doesn't work intuitively. The way that actually worked for me was:&lt;br&gt;
openclaw configure&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Model&lt;/strong&gt; from the sections list, choose Anthropic, and paste your &lt;code&gt;sk-ant-&lt;/code&gt; key when prompted.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Starting the gateway — use &lt;code&gt;--force&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Plain &lt;code&gt;openclaw gateway&lt;/code&gt; would start, reach the ready state, then crash with:&lt;br&gt;
Unhandled promise rejection: CIAO PROBING CANCELLED&lt;/p&gt;

&lt;p&gt;This is a bonjour/mDNS issue on Windows. The fix:&lt;br&gt;
openclaw gateway run --force&lt;/p&gt;

&lt;p&gt;This kills anything stuck on port 18789 first and starts clean. After running this as Administrator, my gateway finally stayed alive.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Correct Setup Sequence for Windows
&lt;/h2&gt;

&lt;p&gt;If you're on Windows and want to save yourself a day, here is the exact order of commands that works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open Command Prompt as Administrator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Install OpenClaw&lt;/strong&gt;&lt;br&gt;
npm install -g openclaw@latest&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add your Telegram bot token&lt;/strong&gt;&lt;br&gt;
openclaw channels add --channel telegram --token "YOUR_TELEGRAM_BOT_TOKEN"&lt;br&gt;
(Get your token from &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt; on Telegram — send it &lt;code&gt;/newbot&lt;/code&gt; and follow the steps)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Set your Claude API key&lt;/strong&gt;&lt;br&gt;
openclaw configure&lt;br&gt;
Select Model → Anthropic → paste your &lt;code&gt;sk-ant-&lt;/code&gt; key&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Start the gateway&lt;/strong&gt;&lt;br&gt;
openclaw gateway run --force&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Wait for this line:&lt;/strong&gt;&lt;br&gt;
[gateway] ready&lt;/p&gt;

&lt;p&gt;That's it. You're running.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I Actually Learned About OpenClaw
&lt;/h2&gt;

&lt;p&gt;Here's the thing — even through all the frustration, I came away genuinely impressed by what OpenClaw is trying to do.&lt;/p&gt;

&lt;p&gt;The skill system is clever. A &lt;code&gt;SKILL.md&lt;/code&gt; file with plain-English instructions that the AI interprets at runtime — no complex APIs, no schema, just natural language. That's the right abstraction for this kind of tool. It means anyone can build a skill, not just developers.&lt;/p&gt;

&lt;p&gt;The channel architecture is smart too. The same agent, the same skills, accessible through Telegram, WhatsApp, Discord — whatever messaging app you're already using. That's meaningful for accessibility specifically: you're not asking someone with a disability to learn a new interface. You're meeting them where they already are.&lt;/p&gt;

&lt;p&gt;The Windows experience is rough right now. The docs assume Unix. The errors are cryptic. The setup is slower than it should be. But the core idea — a personal AI that lives on your own machine, uses your own API keys, and does exactly what you tell it — is genuinely different from everything else out there.&lt;/p&gt;


&lt;h2&gt;
  
  
  What ClearView Would Look Like (When I Finish It)
&lt;/h2&gt;

&lt;p&gt;For anyone who wants to build the accessibility skill I was attempting, here's the &lt;code&gt;SKILL.md&lt;/code&gt; I wrote — it just needs a working gateway underneath it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clearview&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Accessibility assistant. Describes images, reads text, narrates screen content for blind and low-vision users.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## ClearView&lt;/span&gt;

When the user sends an image or photo:
&lt;span class="p"&gt;1.&lt;/span&gt; Describe everything visible in plain simple English
&lt;span class="p"&gt;2.&lt;/span&gt; Read ALL text visible — buttons, menus, labels, errors
&lt;span class="p"&gt;3.&lt;/span&gt; Describe layout — top, middle, bottom, left, right
&lt;span class="p"&gt;4.&lt;/span&gt; Describe what buttons and icons do, not just what they look like
&lt;span class="p"&gt;5.&lt;/span&gt; End with a one sentence summary

When the user sends a photo of a document, letter, or printed text:
&lt;span class="p"&gt;1.&lt;/span&gt; Read all text from top to bottom
&lt;span class="p"&gt;2.&lt;/span&gt; Note headings and structure
&lt;span class="p"&gt;3.&lt;/span&gt; Summarise what it is about

When asked "what's on my screen?":
&lt;span class="p"&gt;1.&lt;/span&gt; Take a screenshot
&lt;span class="p"&gt;2.&lt;/span&gt; Describe it fully as above

Rules:
&lt;span class="p"&gt;-&lt;/span&gt; Say "The screen shows..." not "I can see..."
&lt;span class="p"&gt;-&lt;/span&gt; Always prioritise text over design descriptions
&lt;span class="p"&gt;-&lt;/span&gt; If blurry or unclear, say so honestly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that in &lt;code&gt;~/.openclaw/workspace/skills/clearview/SKILL.md&lt;/code&gt; and restart your gateway. That's the whole skill.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Take
&lt;/h2&gt;

&lt;p&gt;Most OpenClaw posts you'll read this week are going to be polished success stories. Person builds thing, thing works, here are the screenshots.&lt;/p&gt;

&lt;p&gt;This isn't that. I spent a full day on setup, hit six different errors, and didn't finish the project I set out to build.&lt;/p&gt;

&lt;p&gt;But I think that's worth writing about too — because the Windows experience is a real gap, and the next person who hits &lt;code&gt;CIAO PROBING CANCELLED&lt;/code&gt; at midnight deserves to find an answer.&lt;/p&gt;

&lt;p&gt;OpenClaw has a lot of potential. The Windows setup needs work. Both things are true.&lt;/p&gt;

&lt;p&gt;I'll finish ClearView. Just not today.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you built something for accessibility with OpenClaw? Or hit different errors on Windows? Drop them in the comments — I want to know what I missed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
    </item>
    <item>
      <title>I Built an App That Listens to Your Garden and Tells You What's Disappearing. 🌍</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Fri, 17 Apr 2026 23:03:13 +0000</pubDate>
      <link>https://dev.to/_boweii/i-built-an-app-that-listens-to-your-garden-and-tells-you-whats-disappearing-3g92</link>
      <guid>https://dev.to/_boweii/i-built-an-app-that-listens-to-your-garden-and-tells-you-whats-disappearing-3g92</guid>
      <description>&lt;h1&gt;
  
  
  I Built an App That Listens to Your Garden and Tells You What's Disappearing 🌍
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-04-16"&gt;Weekend Challenge: Earth Day Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I tested this, I pointed my mic at my garden one evening.&lt;/p&gt;

&lt;p&gt;Gemini picked up a cricket, a crow, a frog, and a small passerine bird within ten seconds.&lt;/p&gt;

&lt;p&gt;Every single one came back DD - Data Deficient.&lt;/p&gt;

&lt;p&gt;Not endangered. Not safe. Just... unknown. The IUCN doesn't have enough data to even assign them a threat level. They exist. We just haven't studied them enough to know if they're okay.&lt;/p&gt;

&lt;p&gt;That felt more unsettling than a red label would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Last Breath&lt;/strong&gt; is a real-time acoustic biodiversity monitor that runs entirely in the browser. You press record, point your microphone at any outdoor environment for ten seconds, and it tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every species present in that soundscape — identified by &lt;strong&gt;acoustic signature alone&lt;/strong&gt; using Google Gemini 2.5 Flash&lt;/li&gt;
&lt;li&gt;The real &lt;strong&gt;IUCN Red List&lt;/strong&gt; conservation status of each detected species&lt;/li&gt;
&lt;li&gt;Whether their population is increasing, stable, or in freefall&lt;/li&gt;
&lt;li&gt;For the ones that are falling — roughly how many &lt;strong&gt;breeding seasons remain&lt;/strong&gt; in your region&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;biodiversity health score&lt;/strong&gt; from 0–100 for the soundscape you just recorded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every sighting can be logged as a verified conservation record through Auth0 authentication and stored in a &lt;strong&gt;live global map&lt;/strong&gt; shared across every user of the app in real time.&lt;/p&gt;

&lt;p&gt;The world is going quiet. Most people have no idea it's happening outside their window. Last Breath makes it impossible to ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔴 &lt;strong&gt;&lt;a href="https://last-breadth.vercel.app" rel="noopener noreferrer"&gt;Try it live → last-breadth.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;📹 &lt;strong&gt;[Watch the demo → &lt;a href="https://www.youtube.com/watch?v=HkCItSU0nvs" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=HkCItSU0nvs&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feprhvuqxdfev2hme783h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feprhvuqxdfev2hme783h.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk6izjsufsdvob2nbz05.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk6izjsufsdvob2nbz05.png" alt=" " width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2s68fld026tj4dul4p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2s68fld026tj4dul4p4.png" alt=" " width="387" height="850"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Best experienced outdoors or near an open window. Early morning gives the richest soundscapes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's what a session looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Press record — a live waveform visualises your soundscape in real time&lt;/li&gt;
&lt;li&gt;Ten seconds. Gemini analyses every acoustic signature present&lt;/li&gt;
&lt;li&gt;Species cards slide in one by one as IUCN data loads in parallel&lt;/li&gt;
&lt;li&gt;One card turns red — &lt;em&gt;Common Swift · Endangered · ↓ Decreasing · ~12 breeding seasons remaining in London&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;You log the sighting. It pins to the global map. Someone in Lagos, Tokyo, or São Paulo sees it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Boweii22" rel="noopener noreferrer"&gt;
        Boweii22
      &lt;/a&gt; / &lt;a href="https://github.com/Boweii22/LastBreadth" rel="noopener noreferrer"&gt;
        LastBreadth
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Last Breath&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An acoustic biodiversity monitor that listens to the world and tells you what's disappearing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Point a microphone at any outdoor environment — a garden, a forest edge, a park at dawn — and Last Breath identifies every species by sound, cross-references each one against the IUCN Red List, and gives you a real-time biodiversity health score for that place. Verified sightings are logged to a live global map shared across all users.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;We are living through the sixth mass extinction. Most people have no idea it is happening outside their window. Acoustic monitoring — listening for the species present in a soundscape — is one of the most sensitive and non-invasive ways to measure biodiversity. Field researchers have done this for decades with expensive equipment and months of manual analysis. Last Breath brings the same capability to anyone with a browser.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Record a 10-second clip&lt;/em&gt;…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Boweii22/LastBreadth" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why this exists
&lt;/h3&gt;

&lt;p&gt;Apps like Merlin and BirdNET already identify birds by sound. But they stop at identification — they tell you &lt;em&gt;what&lt;/em&gt; is there, not &lt;em&gt;what it means&lt;/em&gt;. They don't show you that the species you just heard is in freefall. They don't give you a way to act on it. That gap is what Last Breath fills.&lt;/p&gt;

&lt;h3&gt;
  
  
  The audio pipeline
&lt;/h3&gt;

&lt;p&gt;When you press record, two things happen simultaneously:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;MediaRecorder&lt;/code&gt; captures the audio stream as a blob&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;AnalyserNode&lt;/code&gt; reads frequency data 60 times per second and draws a live waveform on canvas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After ten seconds the blob converts to base64 and hits &lt;code&gt;/api/analyse&lt;/code&gt; — a Vercel serverless function that injects the Gemini API key server-side and forwards raw audio to Gemini 2.5 Flash with a bioacoustician system prompt. No preprocessing. No spectrograms. Raw audio in, structured conservation JSON out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sending audio directly to Gemini — no preprocessing needed&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BIOACOUSTICIAN_PROMPT&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inline_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mime_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio/webm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;base64Audio&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="nx"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The conservation layer
&lt;/h3&gt;

&lt;p&gt;For each detected species, two parallel IUCN API calls fire immediately — threat category + population trend, and habitat narrative. All lookups run with &lt;code&gt;Promise.allSettled()&lt;/code&gt; so a failed lookup never blocks the others. Cards render progressively as each resolves.&lt;/p&gt;

&lt;p&gt;The biodiversity health score is calculated from the threat categories present:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Score impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CR — Critically Endangered&lt;/td&gt;
&lt;td&gt;−15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EN — Endangered&lt;/td&gt;
&lt;td&gt;−10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VU — Vulnerable&lt;/td&gt;
&lt;td&gt;−6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NT — Near Threatened&lt;/td&gt;
&lt;td&gt;−3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LC — Least Concern&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DD — Data Deficient&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;LC is the only status that &lt;em&gt;adds&lt;/em&gt; to the score — hearing healthy species is a positive signal. The score renders as an animated SVG ring gauge, colour-coded green above 70, amber 40–70, red below 40.&lt;/p&gt;

&lt;h3&gt;
  
  
  The extinction countdown
&lt;/h3&gt;

&lt;p&gt;This was the hardest design decision. Showing "~12 breeding seasons remaining" is blunt. But abstract statistics about wildlife decline stop meaning anything after a while — they become wallpaper. A countdown attached to a sound you just heard outside your window in London is different. It's not about the Amazon. It's about your garden. That specificity is the whole point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Every external API call is proxied through three Vercel serverless functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;api/analyse.js&lt;/code&gt; — injects &lt;code&gt;GEMINI_API_KEY&lt;/code&gt;, handles 429/503 retries with automatic model fallback&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api/iucn.js&lt;/code&gt; — injects &lt;code&gt;IUCN_API_TOKEN&lt;/code&gt;, handles the two-step v4 taxa → assessment lookup&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api/sightings.js&lt;/code&gt; — injects &lt;code&gt;SUPABASE_SERVICE_KEY&lt;/code&gt;, validates all reads and writes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open DevTools on the live app — you will not find a single API key anywhere in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  The global map
&lt;/h3&gt;

&lt;p&gt;Sightings live in Supabase PostgreSQL. Every user sees every other user's observations — not just their own. The map uses Leaflet with CartoDB Dark Matter tiles, markers colour-coded by IUCN status. The goal was a living document — a record of what the world still sounds like, built collaboratively by people who stopped and listened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vanilla HTML, CSS, JS — zero frameworks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI analysis&lt;/td&gt;
&lt;td&gt;Google Gemini 2.5 Flash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conservation data&lt;/td&gt;
&lt;td&gt;IUCN Red List API v4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Auth0 Universal Login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Supabase (PostgreSQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;td&gt;Leaflet.js + CartoDB Dark Matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Vercel (static + serverless functions)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Google Gemini&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gemini 2.5 Flash is the entire engine of this application. It receives raw audio — no preprocessing, no feature extraction — and identifies species by acoustic signature alone, returning structured conservation-ready JSON. I used the multimodal audio input capability via the &lt;code&gt;generateContent&lt;/code&gt; API with an inline base64 audio blob. The bioacoustician system prompt was iterated across many real outdoor recordings to balance species recall with reliable output. The &lt;code&gt;api/analyse.js&lt;/code&gt; function includes automatic retry logic and model fallback for overload responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Auth0 for Agents&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Auth0 Universal Login handles all authentication via the SPA SDK. The auth layer is the trust mechanism for the shared global map — only verified, authenticated users can write to Supabase. Every logged sighting stores the user's Auth0 &lt;code&gt;sub&lt;/code&gt; ID and display name alongside the observation, making every map pin attributable to a real verified observer. This transforms the map from a database into a ledger of provable human action — which felt important for something intended to be taken seriously as a conservation record.&lt;/p&gt;




&lt;p&gt;73% of the world's wildlife has disappeared since 1970. That number is so large it stops meaning anything.&lt;/p&gt;

&lt;p&gt;Last Breath is an attempt to make it small again. Local. Personal. The size of a garden in London.&lt;/p&gt;

&lt;p&gt;We are not going to solve the sixth mass extinction with a web app. But we might start building a record of what we still have. And records matter. Silence is harder to ignore when you've heard what it's replacing.&lt;/p&gt;

&lt;p&gt;Point your mic at the world. Find out what's still there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://last-breadth.vercel.app" rel="noopener noreferrer"&gt;last-breadth.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>Trace AI: I Pointed a Camera at a Whiteboard. Notion Built the Entire System Design Doc.</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sun, 29 Mar 2026 21:03:36 +0000</pubDate>
      <link>https://dev.to/_boweii/trace-ai-i-pointed-a-camera-at-a-whiteboard-notion-built-the-entire-system-design-doc-37m6</link>
      <guid>https://dev.to/_boweii/trace-ai-i-pointed-a-camera-at-a-whiteboard-notion-built-the-entire-system-design-doc-37m6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every engineering team has the same graveyard: folders full of blurry whiteboard photos that were supposed to become&lt;br&gt;&lt;br&gt;
  documentation. They never do. The meeting ends, the momentum dies, and that brilliant architecture sketch slowly rots in&lt;br&gt;&lt;br&gt;
  someone's camera roll.&lt;/p&gt;

&lt;p&gt;Trace AI kills that problem dead.&lt;/p&gt;

&lt;p&gt;Trace is an autonomous pipeline that watches your Notion Design Inbox, your Slack workspace, and your Discord server&lt;br&gt;&lt;br&gt;
  simultaneously. The moment you drop in a whiteboard photo, Trace wakes up, reasons through the sketch using Claude vision,&lt;br&gt;&lt;br&gt;
  and uses the official Notion MCP server to build a complete, structured system design document — entirely on its own.       &lt;/p&gt;

&lt;p&gt;Not a summary. Not a description. A full engineering document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mermaid.js architecture diagrams — flowchart and sequence, auto-generated from your ink&lt;/li&gt;
&lt;li&gt;Component breakdown — every service, database, and load balancer identified and described&lt;/li&gt;
&lt;li&gt;Security analysis — potential vulnerabilities flagged with recommendations&lt;/li&gt;
&lt;li&gt;Bottleneck detection — performance risks spotted before they hit production&lt;/li&gt;
&lt;li&gt;Actionable task cards — your handwritten To-Dos extracted and created as real entries in your Notion engineering board,
with Priority, Category, and Status pre-filled&lt;/li&gt;
&lt;li&gt;AWS cost estimates — rough monthly infrastructure projections for every component&lt;/li&gt;
&lt;li&gt;Complexity scoring — team size and build timeline estimates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire process takes under 2 minutes from photo to polished doc.&lt;/p&gt;

&lt;p&gt;In a large company, information decays. A project manager marks a project as "completed", but the task database still has 5 open items. A budget page says '$10k', but the invoice page says '$12k'. Usually, these contradictions stay hidden until something breaks.&lt;/p&gt;

&lt;p&gt;Sentinel fixes this. Using the new Model Context Protocol (MCP), Sentinel acts as a "shared brain" across your entire workspace. It observes every edit and cross-references it against a set of global truths you define. If a contradiction occurs, Sentinel doesn't just watch—it acts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/5n0E7xTc50c"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Boweii22/Arch-Vision" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Arch-Vision&lt;/a&gt;&lt;br&gt;
Deployed live on Render using Docker (nikolaik/python-nodejs for Python + Node.js in one container — required to run the MCP&lt;br&gt;
server subprocess).&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;This is where Trace AI goes beyond a simple API wrapper.&lt;/p&gt;

&lt;p&gt;Trace spawns the official @notionhq/notion-mcp-server as a live subprocess using the MCP Python SDK. Claude then enters an&lt;br&gt;&lt;br&gt;
  agentic tool-use loop — it doesn't receive a rigid set of instructions and execute them blindly. It reasons about what&lt;br&gt;&lt;br&gt;
  blocks to append, how to structure the content, and how to handle edge cases like long Mermaid diagrams that exceed Notion's&lt;br&gt;
   2000-character rich text limit.&lt;/p&gt;

&lt;p&gt;Whiteboard photo&lt;br&gt;
↓&lt;br&gt;
Claude Vision → Architecture Analysis (structured JSON)&lt;br&gt;
↓&lt;br&gt;
npx @notionhq/notion-mcp-server ←→ Claude agentic loop&lt;br&gt;
↓                                        ↓&lt;br&gt;
Notion Page created                 MCP tool calls:&lt;br&gt;
(direct API, correct parent)        - append_block_children&lt;br&gt;
                                    - create_database_page&lt;br&gt;
                                    - (up to 25 iterations)&lt;br&gt;
↓&lt;br&gt;
Tasks Database ← action items extracted&lt;br&gt;
Projects DB ← relational mapping&lt;/p&gt;

&lt;p&gt;MCP unlocks something that raw API calls can't: judgment. Claude decides when to split a code block, how to format a&lt;br&gt;&lt;br&gt;
  callout, when to skip a section because the data isn't there. The document isn't templated — it's reasoned.&lt;/p&gt;

&lt;p&gt;On top of that, Trace runs a bi-directional sync loop: if you manually edit the Mermaid diagram in Notion, Trace detects the   change, re-analyzes the modified architecture, and updates the analysis sections to stay consistent with your edits. Your&lt;br&gt;&lt;br&gt;
  Notion workspace stays alive, not static.&lt;/p&gt;

&lt;p&gt;Three ways to trigger it — Notion Inbox, Slack, Discord — because great tools meet teams where they already live.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>The 8-Month Feature Nobody Wanted (Including Me, Eventually)</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Thu, 26 Mar 2026 23:26:18 +0000</pubDate>
      <link>https://dev.to/_boweii/the-8-month-feature-nobody-wanted-including-me-eventually-4fg9</link>
      <guid>https://dev.to/_boweii/the-8-month-feature-nobody-wanted-including-me-eventually-4fg9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built something so obsessively specific to my own workflow that I convinced myself it was universal. It wasn't. The moment I stopped refreshing analytics, it shipped better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I Solved (For Myself)
&lt;/h2&gt;

&lt;p&gt;I was drowning in a particular flavor of repetitive work. Not a &lt;em&gt;common&lt;/em&gt; problem—my problem. The kind where you're the only one in your Slack thread who really gets why it's broken. So I decided: I'll build the fix.&lt;/p&gt;

&lt;p&gt;Eight months. Custom parsing logic, a UI that only I understood, database schema decisions that made sense only because I lived in that headspace daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Part
&lt;/h2&gt;

&lt;p&gt;I launched it to silence. Not criticism silence—&lt;em&gt;indifference&lt;/em&gt; silence. I refreshed GitHub stars. Checked analytics every 6 hours. Posted in communities. Nothing. The feature was &lt;em&gt;objectively good&lt;/em&gt;—it worked, it was fast, it solved the problem &lt;em&gt;it was designed for&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But it was designed for an audience of one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed Everything
&lt;/h2&gt;

&lt;p&gt;One afternoon I just... stopped checking. Deleted the analytics bookmark. Moved on to the next thing. &lt;/p&gt;

&lt;p&gt;That's when something weird happened: people started using it. Not because it went viral. But because the moment I stopped optimizing for adoption, I started shipping &lt;em&gt;faster&lt;/em&gt;. I added features because &lt;em&gt;I&lt;/em&gt; needed them. I fixed bugs I actually encountered. The product got honest.&lt;/p&gt;

&lt;p&gt;Turns out people can smell when you're building for them versus building for yourself. They prefer the latter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Specific beats universal.&lt;/strong&gt; A tool built with religious specificity for one person often beats a generic solution built for "everyone."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics are a trap.&lt;/strong&gt; The refresh-check-despair loop kills momentum. You don't need permission to build—you need focus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping &amp;gt; perfecting.&lt;/strong&gt; The moment I stopped trying to predict what others wanted, I built something worth using.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The real victory wasn't adoption. It was finishing something.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're sitting on a feature you're "shocked nobody wants"—that's not failure feedback. That's a signal you're too deep in your own head. Either go deeper (own your specificity) or ship it and forget about it. The middle ground—optimizing for invisible users—is where projects die.&lt;/p&gt;

&lt;p&gt;What's the feature you've been gatekeeping because you think nobody else cares?&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>startup</category>
    </item>
    <item>
      <title>Remote Work Didn't Fix Toxic Culture—It Just Made It Async</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Thu, 26 Mar 2026 19:32:57 +0000</pubDate>
      <link>https://dev.to/_boweii/remote-work-didnt-fix-toxic-culture-it-just-made-it-async-5hgj</link>
      <guid>https://dev.to/_boweii/remote-work-didnt-fix-toxic-culture-it-just-made-it-async-5hgj</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Your team's dysfunction didn't disappear when everyone went remote. It just moved into Slack, and now it's worse because you can't read the room.&lt;/p&gt;

&lt;p&gt;I spent 3 years watching this play out. Pre-pandemic, your manager's bad mood was obvious—you'd catch it in a hallway conversation. Now? That same manager writes passive-aggressive comments in PRs at 11 PM and you're supposed to interpret tone through a screen.&lt;/p&gt;

&lt;p&gt;The real problem: &lt;strong&gt;We mistook silence for health.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remote work did expose one thing clearly—bad code, bad decisions, bad people all leave a digital paper trail. But instead of fixing the culture, teams just learned to code-switch better. The senior engineer who bullied juniors in meetings? Now they bully them in GitHub comments. The meeting-obsessed manager? Just scheduled 8 async updates instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;p&gt;Old culture toxicity:&lt;br&gt;
  visibility = high&lt;br&gt;
  confrontation = forced&lt;br&gt;
  resolution = sometimes happens&lt;/p&gt;

&lt;p&gt;Remote culture toxicity:&lt;br&gt;
  visibility = hidden in timestamps&lt;br&gt;
  confrontation = deniable&lt;br&gt;
  resolution = "that's just how they communicate"&lt;/p&gt;

&lt;p&gt;The dysfunction didn't change. The accountability did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Culture is a system, not a setting. Flipping "remote" doesn't rewrite how people treat each other—it just changes the medium. I've seen amazing remote teams and soul-crushing ones. The difference? Teams that actually addressed &lt;em&gt;why&lt;/em&gt; people were unhappy, not just &lt;em&gt;where&lt;/em&gt; they worked.&lt;/p&gt;

&lt;p&gt;The teams that thrived remotely did something radical: they explicitly named their values, called out bad behavior in public channels, and held leadership accountable the same way they held junior devs accountable in code review.&lt;/p&gt;

&lt;p&gt;It took work. It wasn't comfortable. But it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Curious About
&lt;/h2&gt;

&lt;p&gt;Have you seen this in your org? Did remote work unmask problems or just rearrange them? And more importantly—did your team actually address the culture, or just accept a quieter version of the same dysfunction?&lt;/p&gt;

&lt;p&gt;Drop a comment. I'm betting you've got a story.&lt;/p&gt;

</description>
      <category>remotework</category>
      <category>devculture</category>
      <category>techleadership</category>
      <category>culturematters</category>
    </item>
    <item>
      <title>My AI Caught a £3,200 Scope Creep at 3am While I Was Asleep—Here's the Notion MCP System I Built</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Tue, 24 Mar 2026 17:27:37 +0000</pubDate>
      <link>https://dev.to/_boweii/my-ai-caught-a-ps3200-scope-creep-at-3am-while-i-was-asleep-heres-the-notion-mcp-system-i-built-1mnj</link>
      <guid>https://dev.to/_boweii/my-ai-caught-a-ps3200-scope-creep-at-3am-while-i-was-asleep-heres-the-notion-mcp-system-i-built-1mnj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to show you something that happened at 3:17 am on a Tuesday.&lt;/p&gt;

&lt;p&gt;I was asleep. My phone was on silent. I wasn't thinking about work.&lt;br&gt;
But my Notion workspace was changing.&lt;/p&gt;

&lt;p&gt;A database row — a client called Holloway Studio — had its health score property quietly drop from 84 to 31. Its status flipped from "On track" to "Breach flagged". A new page appeared in its linked interaction log database: "Scope creep detected — client requested 4 additional deliverables beyond contracted scope in email thread at 23:42."&lt;/p&gt;

&lt;p&gt;And in a drafts section of the same Notion page, a complete, professional email had appeared — written by Claude AI, addressed to the client, asserting the contract terms, proposing a change order. Ready to send. Waiting for me when I woke up.&lt;/p&gt;

&lt;p&gt;I hadn't written a single line of that. I was asleep.&lt;/p&gt;

&lt;p&gt;That's what Notion MCP unlocked for me. Not a chatbot you talk to. It's not a dashboard you check. A system that runs — reads your world, reasons about it, and writes the results back to the place you actually work — while you do literally anything else.&lt;/p&gt;

&lt;p&gt;I built Contract OS. And I want to show you exactly how it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Contract OS is an autonomous AI agent that protects freelancers from the three things that silently drain their income every year:&lt;/p&gt;

&lt;p&gt;Scope creep — clients casually requesting extra work in emails, knowing most freelancers won't push back.&lt;/p&gt;

&lt;p&gt;Late payments — invoices that go a week past due, then two weeks, while you're too busy working to chase them.&lt;/p&gt;

&lt;p&gt;Revision abuse — clients burning through revision rounds the contract doesn't allow because nobody's counting.&lt;/p&gt;

&lt;p&gt;The system connects to Gmail, Google Calendar, and—at the absolute centre of everything—Notion, via the Notion MCP server. Every 6 hours, automatically, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads every active contract from a Notion database&lt;/li&gt;
&lt;li&gt;Fetches recent emails and calendar events for each client&lt;/li&gt;
&lt;li&gt;Sends all of it to Claude to detect breaches and score contract health&lt;/li&gt;
&lt;li&gt;Writes every result back to Notion — health scores, statuses, breach alerts, drafted emails, interaction logs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notion isn't where the results get saved. Notion is the brain. The agent reads from it, reasons against it, and writes back to it in a continuous loop. Every cycle makes the workspace smarter.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;The Landing Page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before the dashboard, there's a landing page that explains the problem, shows the product, and gives you clear next steps whether you want to try the live demo or clone the repo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50gg7bu3y0kxkf2yrnqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50gg7bu3y0kxkf2yrnqz.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl66vyr78jvlg7aqpzr43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl66vyr78jvlg7aqpzr43.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oh9uui8ozris4ibppd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oh9uui8ozris4ibppd7.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/fRoAKqk_djk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Boweii22/Contract-OS" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Contract-OS&lt;/a&gt;, Live Demo: &lt;a href="https://contract-os-dashboard.vercel.app/" rel="noopener noreferrer"&gt;https://contract-os-dashboard.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The live demo uses a seeded Notion workspace with fake client data — safe to explore fully. To run against your own contracts, clone and add your keys. The README walks through everything in about 15 minutes.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Boweii22/Contract-OS
&lt;span class="nb"&gt;cd &lt;/span&gt;contract-os
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env.local
&lt;span class="c"&gt;# Fill in: ANTHROPIC_API_KEY, NOTION_TOKEN, &lt;/span&gt;
&lt;span class="c"&gt;# NOTION_CONTRACTS_DB_ID, GOOGLE_CLIENT_ID,&lt;/span&gt;
&lt;span class="c"&gt;# GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;This is the part I want to spend real time on — because the Notion MCP integration here is doing something I haven't seen any other project attempt. It's not a one-way pipe. It's a living read/write loop that makes Notion the persistent memory of an AI agent.&lt;/p&gt;

&lt;p&gt;Let me walk through every layer.&lt;/p&gt;

&lt;p&gt;The Notion database schema — contracts as structured AI-readable data&lt;br&gt;
The first thing I built was the Notion database. Two of them, actually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database 1: Contracts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every active client contract lives here as a structured row. Not just a name and a number — every property the AI needs to reason about a potential breach:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What the AI uses it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client name&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;Primary identifier — also used as the Gmail search term to find that client's emails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract value&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Detects payment shortfalls and calculates outstanding balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment terms&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;The AI reads this to judge whether a payment is late&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deposit received&lt;/td&gt;
&lt;td&gt;Checkbox&lt;/td&gt;
&lt;td&gt;Confirms whether the upfront payment has landed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deposit amount&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Cross-referenced against contract value to calculate what's still owed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance due&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Outstanding amount — triggers payment breach detection if overdue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadline&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Calculates deadline proximity risk — penalties kick in inside 14 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Flipped by the AI each cycle: On track / Scope creep / Breach flagged / Payment late&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health score&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Written by the AI — 0 to 100, validated against the deterministic formula&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revision rounds&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;The contracted allowance — the ceiling the AI enforces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revisions used&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Written back by the AI — compared against rounds to detect revision abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notice period days&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Used to flag if a termination risk is approaching without proper notice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract type&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Fixed price / Retainer / Time &amp;amp; materials — affects how payment breach logic runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last signal&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The AI's one-sentence summary of the most recent finding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last synced&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Timestamp of the last completed agent cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client email domain&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;e.g. &lt;code&gt;@acmestudio.com&lt;/code&gt; — how the agent filters Gmail to the right client thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notion log page URL&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;Direct link to the client's interaction log page in Notion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log database ID&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The ID of the linked log database — used by the agent to write new signal entries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;The AI reads this entire row at the start of every cycle. This is what it knows. This is what it checks against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database 2: Interaction Log (one per client, linked sub-page)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every signal detected — every email flagged, every breach logged, every draft created — becomes a structured entry in this database. Timestamp d. Categorised. Permanent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What the AI uses it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;Auto-generated entry title — e.g. &lt;code&gt;PAYMENT_BREACH · Nova Agency&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract&lt;/td&gt;
&lt;td&gt;Relation&lt;/td&gt;
&lt;td&gt;Links back to the parent contract row in the Contracts database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Signal category — &lt;code&gt;SCOPE_CREEP&lt;/code&gt; / &lt;code&gt;PAYMENT_BREACH&lt;/code&gt; / &lt;code&gt;REVISION_BREACH&lt;/code&gt; / &lt;code&gt;FEEDBACK_DELAY&lt;/code&gt; / &lt;code&gt;DEADLINE_RISK&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Severity&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;How serious the finding is — &lt;code&gt;BREACH&lt;/code&gt; / &lt;code&gt;WARNING&lt;/code&gt; / &lt;code&gt;INFO&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;Human-readable explanation of exactly what was detected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The raw data that triggered the signal — quoted email content, calendar events, or contract values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract clause violated&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The specific contract term that was broken or put at risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended action&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;What the agent did next — &lt;code&gt;Draft email&lt;/code&gt; / &lt;code&gt;Log only&lt;/code&gt; / &lt;code&gt;Book meeting&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detected at&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Timestamp of when the signal was fired during the agent cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health score at detection&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;The contract's health score at the exact moment the signal was logged — creates a timeline of decline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the audit trail. Every decision the AI ever made, recorded in Notion forever.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Reading contracts via Notion MCP — the agent's starting point&lt;/strong&gt;&lt;br&gt;
Every agent cycle begins with a single Notion MCP call: query the Contracts database, filter for active contracts, return everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tools/notion.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@notionhq/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_TOKEN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveContracts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_CONTRACTS_DB_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;does_not_equal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;does_not_equal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Archived&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;clientName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contractValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contract value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentTerms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment terms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deadline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;revisionRounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Revision rounds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;revisionsUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Revisions used&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gmailLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Gmail thread label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;logDatabaseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Log database ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;notionPageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the foundation of everything. The agent knows nothing about your contracts except what Notion tells it. Notion is the source of truth.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Claude signal detection — the AI reasoning layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the contract data in hand, the agent fetches the last 15 emails from that client's Gmail thread and the next 30 days of calendar events. Then it sends all of it to Claude in a single, structured prompt — and Claude returns a clean JSON breach report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// prompts/signal-detector.ts&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`You are a contract compliance AI for a freelancer.
You receive contract terms, recent client emails, and calendar data.
Detect any of these breach types:
- SCOPE_CREEP: work requested outside the contract scope
- PAYMENT_BREACH: payment overdue per the agreed terms
- REVISION_BREACH: more revisions requested than contracted
- FEEDBACK_DELAY: client has not responded within their window
- DEADLINE_RISK: deadline within 14 days with unresolved blockers

Respond ONLY with valid JSON. No markdown. No preamble. No explanation.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`CONTRACT TERMS:
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentTerms&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Deadline: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Revision rounds allowed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revisionRounds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Revisions used: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revisionsUsed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

RECENT EMAILS (last 15):
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;emailThreads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

CALENDAR EVENTS (next 30 days):
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;calendarEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

Respond with this exact JSON structure:
{
  "signals": [{
    "type": "SCOPE_CREEP|PAYMENT_BREACH|REVISION_BREACH|FEEDBACK_DELAY|DEADLINE_RISK",
    "severity": "INFO|WARNING|BREACH",
    "description": "One sentence finding",
    "evidence": "Direct quote from the email",
    "recommended_action": "Draft email|Book meeting|Log only",
    "contract_clause_violated": "Which term was breached"
  }],
  "health_score": &amp;lt;0-100&amp;gt;,
  "health_reasoning": "Two sentence explanation"
}`&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The health score Claude returns gets validated against a deterministic formula — breach severity deductions, deadline proximity penalties, revision overuse penalties. The final score written to Notion is always the lower of the two. Claude cannot hallucinate your contract health upward.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Writing results back to Notion MCP — the loop closes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the moment I find most satisfying about this system. After Claude analyses a contract, every single result flows back into Notion via MCP. The database row updates. The interaction log grows. If an email needs drafting, the draft lands in Notion too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tools/notion.ts — write results back after every agent cycle&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateContractHealth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;healthScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;latestSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update the contract database row&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notionPageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Health score&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;healthScore&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Last signal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latestSignal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Last synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;appendInteractionLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Signal&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a new log entry in the client's linked database&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logDatabaseId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Detail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signal level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Action taken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommendedAction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveDraftEmailToNotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The drafted email lives in Notion — survives page refresh,&lt;/span&gt;
  &lt;span class="c1"&gt;// survives server restart, accessible from any device&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logDatabaseId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Draft: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Draft email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Detail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signal level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Breach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Action taken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Claude drafted response — awaiting send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The write-back is what completes the loop. Notion isn't a destination. It's a relay station. The agent reads from it, Claude reasons against it, and the results flow back into it — richer than before, ready for the next cycle to read.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The bidirectional loop — visualised&lt;/strong&gt;&lt;br&gt;
Here's the full cycle, end to end, every 6 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Notion Contracts DB
       │
       │  notion.databases.query() — reads all active contracts
       ▼
  Agent Orchestrator
       │
       ├──► Gmail MCP — fetches last 15 emails per client
       ├──► Calendar MCP — fetches next 30 days of events
       │
       ▼
  Claude claude-sonnet-4-6
  (contract terms + emails + calendar → JSON breach report)
       │
       ▼
  Agent Orchestrator
       │
       ├──► notion.pages.update() — health score, status, last signal
       ├──► notion.pages.create() — new interaction log entry
       └──► notion.pages.create() — draft email saved to Notion
       │
       ▼
Notion Contracts DB
(now richer — ready for the next cycle to read)
       │
       ▼
  SSE broadcast → Dashboard updates in real time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three MCP servers. One agent. One brain — Notion. Every 6 hours, automatically, whether I'm at my desk or asleep.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Real-time dashboard — SSE from agent to UI&lt;/strong&gt;&lt;br&gt;
The dashboard connects to the agent via Server-Sent Events. The moment a contract is processed, the health score updates in the UI without a refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dashboard — useSSE hook&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contract:updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setContracts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signal:detected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setSignalFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The health ring animates. The breach badge appears. The signal card slides in. All from Notion data, flowing through Claude, pushed to the browser in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The FullStack&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI model&lt;/strong&gt; — Claude &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;&lt;br&gt;
Structured JSON output, multi-source reasoning, zero hallucinated health scores&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primary MCP&lt;/strong&gt; — Notion MCP&lt;br&gt;
Reads contracts, writes health scores, creates log entries — the persistent brain&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting MCP&lt;/strong&gt; — Gmail MCP + Google Calendar MCP&lt;br&gt;
Real email threads + real calendar data fed directly into Claude&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent runtime&lt;/strong&gt; — Node.js 20 + TypeScript&lt;br&gt;
Fully typed orchestration, fast async parallel contract processing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduler&lt;/strong&gt; — &lt;code&gt;node-cron&lt;/code&gt;&lt;br&gt;
Runs every 6 hours automatically — no manual triggers, no babysitting&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API + real-time&lt;/strong&gt; — Fastify + Server-Sent Events&lt;br&gt;
Sub-second health score push to dashboard the moment a contract is processed&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State cache&lt;/strong&gt; — &lt;code&gt;lowdb&lt;/code&gt;&lt;br&gt;
Flat-file JSON deduplication — never re-fires a signal Claude already caught&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dashboard&lt;/strong&gt; — Next.js 14 App Router&lt;br&gt;
Server components + streaming for instant first paint&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Animations&lt;/strong&gt; — Framer Motion 11&lt;br&gt;
Spring-based health ring, staggered loads, sliding tab indicator&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Styling&lt;/strong&gt; — Tailwind CSS v4&lt;br&gt;
Utility-first with CSS custom properties for the full design system&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fonts&lt;/strong&gt; — DM Serif Display + DM Mono + Geist&lt;br&gt;
Display headlines, monospace data values, clean UI body text&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosting&lt;/strong&gt; — Railway (agent) + Vercel (dashboard)&lt;br&gt;
Persistent Node server for cron + edge-optimised Next.js — both free tier&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Notion MCP Actually Unlocked&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I want to be specific about this because it's easy to say "Notion MCP is central" and leave it vague. Here's what specifically would not be possible without it:&lt;/p&gt;

&lt;p&gt;Without Notion MCP reading contracts: The agent has no idea what your contract terms are. It cannot judge what's a breach versus what's normal. It's blind.&lt;/p&gt;

&lt;p&gt;Without Notion MCP writing health scores: The results of every AI analysis evaporate the moment the agent process ends. There's no memory. The next cycle starts from zero.&lt;/p&gt;

&lt;p&gt;Without Notion MCP creating log entries: There's no audit trail. No proof of what the AI detected, when it detected it, or what it did about it. The system is a black box.&lt;/p&gt;

&lt;p&gt;Without Notion MCP saving draft emails: The drafted response dies in memory. It never reaches you. It never becomes an action you can take.&lt;br&gt;
Notion MCP is what transforms this from a script that runs once into a system that learns and accumulates — every cycle building on the last, every result persisted, every decision traceable.&lt;/p&gt;

&lt;p&gt;That's the unlock. That's what makes it a product instead of a demo.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What's Next&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Slack + WhatsApp push alerts — breach detected at 3am? Get a message, not just a Notion update&lt;/li&gt;
&lt;li&gt;Contract generator — describe a project in plain English, get a full contract drafted and saved to Notion&lt;/li&gt;
&lt;li&gt;Client portal — a read-only Notion share link showing your client their deliverable statuses in real time&lt;/li&gt;
&lt;li&gt;Stripe webhook — detect payment received automatically, close the payment breach loop without reading emails&lt;/li&gt;
&lt;li&gt;Multi-currency — GBP-first today, trivial to extend&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Try It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Live demo — fake data, fully interactive: contract-os.vercel.app&lt;br&gt;
GitHub — MIT licensed, clone and add your keys: &lt;a href="https://github.com/Boweii22/Contract-OS" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Contract-OS&lt;/a&gt;&lt;br&gt;
Landing page: &lt;a href="https://contract-os-dashboard.vercel.app/" rel="noopener noreferrer"&gt;https://contract-os-dashboard.vercel.app/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you're a freelancer — I want to hear from you in the comments. Tell me about the scope creep that cost you. The invoice that went 30 days past due. The client who asked for "just one more small change" six times in a row.&lt;/p&gt;

&lt;p&gt;My guess is every single person reading this has a story. And I built this because I got tired of those stories being the price of doing business.&lt;/p&gt;

&lt;p&gt;They don't have to be.&lt;/p&gt;




&lt;p&gt;Built for the DEV × Notion MCP Challenge, March 2026.&lt;br&gt;
Claude + Notion MCP + Gmail + Google Calendar + Next.js + Framer Motion&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Mapped My Entire Freelance Client Onboarding Process. It took 6 tools and 73 minutes. Here's What I Found.</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Wed, 18 Mar 2026 12:00:46 +0000</pubDate>
      <link>https://dev.to/_boweii/i-mapped-my-entire-freelance-client-onboarding-process-it-took-6-tools-and-73-minutes-heres-what-326</link>
      <guid>https://dev.to/_boweii/i-mapped-my-entire-freelance-client-onboarding-process-it-took-6-tools-and-73-minutes-heres-what-326</guid>
      <description>&lt;p&gt;And why do I think most freelancers have quietly accepted broken workflows as normal?&lt;/p&gt;




&lt;p&gt;Last month I decided to do something slightly embarrassing: I timed myself.&lt;/p&gt;

&lt;p&gt;Not during the project. Before it. I wanted to know exactly how long it took me to go from "yes, I'm interested in working with you" to "okay, the deposit's paid and we can start."&lt;/p&gt;

&lt;p&gt;The answer was 73 minutes. Across 6 different tools. For a single client.&lt;/p&gt;

&lt;p&gt;I'm a developer who's been freelancing for a few years. I've shipped dozens of projects. I have a favourable reputation. Clients come back. And yet somewhere along the way I'd built this completely invisible tax into every single engagement—a manual, fragmented, cobbled-together workflow that I'd never once stopped to actually look at.&lt;/p&gt;

&lt;p&gt;When I finally looked at it, I couldn't unsee it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Audit: What My "Process" Actually Looked Like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the exact sequence I went through, step by step:&lt;/p&gt;

&lt;p&gt;Step 1: The proposal (~25 minutes)&lt;br&gt;
Open a previous Google Doc. Start a new one — I never have a good template, so I'm always rebuilding from scratch. Write the scope. Second-guess the scope. Rewrite the scope. Format it to look professional. Export to PDF. Realise the PDF looks weird. Reformat. Export again.&lt;/p&gt;

&lt;p&gt;Step 2: The contract (~15 minutes)&lt;br&gt;
Open HelloSign (now Dropbox Sign). Find a contract template I saved two years ago. Update the client name, dates, and project scope. Send it. Wait for the client to create an account before they can even open it.&lt;/p&gt;

&lt;p&gt;Step 3: The deposit (~10 minutes)&lt;br&gt;
Send a PayPal invoice. Wonder if the client has PayPal. They don't always. Explain bank transfer as an alternative. Wait for them to figure out international transfer fees. Occasionally chase it 3 days later when it hasn't arrived.&lt;/p&gt;

&lt;p&gt;Step 4: The confirmation (~8 minutes)&lt;br&gt;
Email back and forth to confirm receipt of deposit, confirm project start date, and confirm which version of the scope we're working from.&lt;/p&gt;

&lt;p&gt;Step 5: The actual invoice (later, but it counts)&lt;br&gt;
Weeks later, open Wave (my invoice tool). Recreate the project details from memory. Send. Sometimes I get asked, "What is this for again?" because it looks nothing like the proposal.&lt;/p&gt;

&lt;p&gt;Total: ~73 minutes of admin, 6 different tools, zero of which talk to each other.&lt;/p&gt;

&lt;p&gt;And here's the part that really got me: none of this is visible in my work. A client sees a beautiful final deliverable. They have no idea how chaotic the paper trail behind it was.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Real Cost Nobody Talks About&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The obvious cost is time. 73 minutes per client adds up fast — if you onboard 15 clients a year, that's 18 hours of pure admin before a single line of code or pixel is moved.&lt;/p&gt;

&lt;p&gt;But the less obvious cost is professionalism.&lt;/p&gt;

&lt;p&gt;There's a gap between the quality of most freelancers' work and the quality of their onboarding process. You might have an incredible portfolio, strong references, and 5-star reviews — and then your client receives a Google Doc, a separate DocuSign email, a PayPal request, and three emails trying to stitch it all together.&lt;/p&gt;

&lt;p&gt;It doesn't match. And I think clients feel it even if they can't articulate it.&lt;/p&gt;

&lt;p&gt;A designer friend of mine put it best: "My proposals look nothing like my actual design work. My work is clean and intentional. My proposals are... a PDF I made in 2021 that I keep patching."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;I Asked Around. Turns Out This Is Almost Universal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After doing my own audit, I started asking other freelancers how they handle this. Developers, designers, photographers, copywriters, videographers. The answers were remarkably consistent:&lt;/p&gt;

&lt;p&gt;The most common setup (probably 60% of people I asked):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google Docs or Word → PDF email → HelloSign/DocuSign → PayPal or bank transfer → Wave/FreshBooks invoice&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The aspirational setup (people who'd invested in proper tools):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HoneyBook or Bonsai or Dubsado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The honest feedback about those proper tools:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Too expensive." / "Way more than I need." / "Took me weeks to set up and I barely used half the features." / "Designed for agencies, not individual freelancers."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HoneyBook is $39–79/month. Bonsai is $25–52/month. Dubsado has a learning curve that rivals some SaaS products I've been paid to build. These are serious tools for serious agencies. They're not really built for someone who wants to send a proposal and get paid.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Specific Things That Are Broken&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After all these conversations, here's what I think are the actual broken parts — in order of how painful they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's no standard proposal format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every freelancer I spoke to described starting proposals from scratch or from an old document they keep patching. There's no "proposal.template" you can pull up and trust. Every client feels like the first client in terms of the process.&lt;/p&gt;

&lt;p&gt;The result: enormous variation in quality and a massive time sink on something that should be repeatable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proposals are static dead documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You send a PDF. The client receives it. You have zero idea what happens next. Did they open it? Did they read it? Did they forward it to someone else? Did they misplace it? You're flying blind from the moment you hit send.&lt;/p&gt;

&lt;p&gt;The anxiety of not knowing is surprisingly real. I've sent proposals and then spent three days wondering if the email even arrived.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The signature and payment are separate, awkward steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now, for most freelancers, a client has to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a DocuSign/ HelloSign email and create an account to sign&lt;/li&gt;
&lt;li&gt;Then separately figure out PayPal or bank transfer to pay the deposit&lt;/li&gt;
&lt;li&gt;Then email you to confirm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is three friction points between "yes, I want to work with you" and "money is moving." Each one is a moment where a client can lose momentum, get distracted, or decide it's more trouble than it's worth.&lt;/p&gt;

&lt;p&gt;I genuinely believe some clients ghost after receiving proposals not because they're not interested but because the process of saying yes is annoying enough that they put it off until they forget.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tools don't talk to each other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your proposal lives in Google Docs. Your contract lives in HelloSign. Your payment lives in PayPal. Your invoice lives in Wave. When you need to reference "what did we agree to" six weeks into a project, you're digging through four different apps trying to reconstruct the truth.&lt;/p&gt;

&lt;p&gt;There's no single source of record. And when disputes happen (they do), this matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything has to be rebuilt for every client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scope of the proposal changes per client. That's expected and fine. But the structure of the proposal? The payment terms? The contract language? The invoice format? These should be templates. d. They rarely are.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What a Better Version Looks Like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been thinking about what the ideal workflow actually looks like for an independent freelancer (not an agency, not a team — one person with good work and a handful of clients at any given time).&lt;/p&gt;

&lt;p&gt;Here's what I want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I fill in a form — client name, project, scope, pricing, timeline. Two minutes.&lt;/li&gt;
&lt;li&gt;A link is generated — a clean, professional-looking page that the client can open. Not a PDF attachment. A live URL.&lt;/li&gt;
&lt;li&gt;The client reads, signs, and pays — all on the same page. No separate tools. No account creation. Type their name to sign, enter card, done.&lt;/li&gt;
&lt;li&gt;I get notified, and funds move — I know the moment they opened it, signed it, and paid.&lt;/li&gt;
&lt;li&gt;Everything lives in one place — one dashboard with all proposals, their status, and the ability to convert any signed proposal into a final invoice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No CRM. No time tracking. No project management. Just create a proposal, send a link, and get paid.&lt;/p&gt;

&lt;p&gt;The version I have in my head is something like $15–20/month. Not $60/month. Not a 2-hour setup process. Something I could have running and used within the same afternoon I signed up.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Broader Pattern: Freelancers Are Remarkably Bad at Selling to Themselves&lt;/strong&gt;&lt;br&gt;
Here's the thing that strikes me most about all of this.&lt;/p&gt;

&lt;p&gt;Freelancers — especially developers and designers — are often incredibly good at building products. We understand UX. We know what makes a clean, frictionless flow. We can spot a broken onboarding process in a client's product from a mile away.&lt;/p&gt;

&lt;p&gt;And then we go home and send proposals in Google Docs and chase deposits over PayPal.&lt;/p&gt;

&lt;p&gt;There's a weird blind spot where we've accepted our own workflow as "just how it is" while we'd never let a client ship a product with that many friction points.&lt;/p&gt;

&lt;p&gt;I think part of it is that nobody teaches you this stuff. You start freelancing, you send your first proposal however you can, and then you just keep doing it that way. The tools you'd need to fix it cost too much or require too much setup, so the path of least resistance is to keep doing the broken thing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I'm Actually Curious About&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've shared my setup (chaos) and what I think the ideal looks like (one link, everything in one place). But I'm genuinely curious what other people have found.&lt;/p&gt;

&lt;p&gt;Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long does your client onboarding take — from first "yes, interested" to deposit paid and project started?&lt;/li&gt;
&lt;li&gt;What's the actual worst part — is it the proposal writing, the contract, the deposit, the back-and-forth?&lt;/li&gt;
&lt;li&gt;If you've tried a proper tool (HoneyBook, Bonsai, Dubsado, anything else) — did it stick? Why or why not?&lt;/li&gt;
&lt;li&gt;What would you pay for something that genuinely solved this? Or does it not bother you enough to pay anything?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your answers in the comments. Genuinely collecting these — not for any agenda, just because I think this problem is more interesting than it looks on the surface.&lt;/p&gt;

</description>
      <category>freelance</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>The Loneliness of Being the Only Dev in the Room</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Wed, 18 Mar 2026 05:24:33 +0000</pubDate>
      <link>https://dev.to/_boweii/the-loneliness-of-being-the-only-dev-in-the-room-fe2</link>
      <guid>https://dev.to/_boweii/the-loneliness-of-being-the-only-dev-in-the-room-fe2</guid>
      <description>&lt;p&gt;Nobody writes about this. Maybe because the people who live it are too busy being the only ones who can fix the WiFi.&lt;/p&gt;

&lt;p&gt;There's a specific kind of loneliness that has no name yet.&lt;/p&gt;

&lt;p&gt;It's not the loneliness of working remotely. It's not the loneliness of being an introvert in an open-plan office. It's sharper than both of those and quieter.&lt;/p&gt;

&lt;p&gt;It's the loneliness of being the only developer in a company full of people who don't speak your language — and never will.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You didn't notice it at first&lt;/strong&gt;&lt;br&gt;
The job sounded exciting. A real company. Real problems. And you—the person who could actually build things—walking in like someone who finally has superpowers in a world that needs them.&lt;/p&gt;

&lt;p&gt;The first few months felt like that. You shipped things. People were impressed. Someone in a meeting called you "our tech guy/girl", and you smiled because it felt like belonging.&lt;/p&gt;

&lt;p&gt;Then, slowly, quietly, you started to notice.&lt;/p&gt;

&lt;p&gt;There was no one to review your pull requests. Not because people were lazy — but because nobody could. You merged your own code. You reviewed your own architecture decisions. You Googled your own doubts.&lt;/p&gt;

&lt;p&gt;You started having conversations with yourself dressed up as documentation.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The weight of being the only one who knows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what nobody tells you about being the sole developer:&lt;/p&gt;

&lt;p&gt;You never get to be junior again.&lt;/p&gt;

&lt;p&gt;In a team, you can say, "I'm not sure; let me check with someone." You can say, "I think we should do X, but I want a second opinion." " You can be wrong in a safe container where someone catches you.&lt;/p&gt;

&lt;p&gt;When you're alone, every decision is final until it catastrophically isn't. The codebase becomes a direct mirror of your blind spots — and you can't see your blind spots. That's what makes them blind spots.&lt;/p&gt;

&lt;p&gt;The worst part? You can't tell anyone at work. Because to them, you're the expert. The wizard. The one with the answers. Admitting uncertainty feels like the whole illusion shatters.&lt;/p&gt;

&lt;p&gt;So you carry it. You carry the uncertainty, the technical debt you made at 2am when something was on fire, the architectural decisions you're not proud of, and the library you chose because it was familiar, not because it was right.&lt;/p&gt;

&lt;p&gt;You carry it alone.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The meetings. Oh, the meetings.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You sit in a room where someone is explaining a problem using words that mean something different to you than to them.&lt;/p&gt;

&lt;p&gt;"Can't we just make it automatic?"&lt;br&gt;
"How long would it take to just connect the two systems?"&lt;/p&gt;

&lt;p&gt;"We had something like this at my last company; they built it in a weekend."&lt;/p&gt;

&lt;p&gt;And you smile. Because explaining why it's not that simple requires first explaining what a system even is, what an API is, and what "connecting" two things actually means at 3am when the data formats don't match and one of the services has been deprecated and the documentation is a lie.&lt;br&gt;
You smile. You say, "I'll look into it." " You go back to your desk and stare at the ceiling for ten minutes.&lt;/p&gt;

&lt;p&gt;This isn't frustration at your colleagues. They're smart. They're good at what they do. This is something else — the particular ache of expertise becoming isolation.&lt;/p&gt;

&lt;p&gt;The more you know, the more alone you are in knowing it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You become the IT department by accident&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At some point — and you won't remember exactly when — you became responsible for things nobody assigned you.&lt;/p&gt;

&lt;p&gt;The printer. The WiFi. "My laptop is slow." "Can you look at this spreadsheet? Something's wrong with the formula." "We need a new tool for X; can you find one?" "Can you just automate this thing we do every Tuesday?"&lt;/p&gt;

&lt;p&gt;You say yes. Because you're kind. Because it's easier. Because saying "that's not really my job" when you're the only technical person feels like abandonment.&lt;/p&gt;

&lt;p&gt;But something quietly breaks inside you each time. Not because the tasks are beneath you. But because each one is a reminder: you are not here as a peer. You are here as infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The thing that actually hurts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's not the workload. It's not even the loneliness in the textbook sense.&lt;/p&gt;

&lt;p&gt;It's the absence of intellectual companionship.&lt;/p&gt;

&lt;p&gt;There's a specific joy that exists only when someone else deeply understands what you're building. When you can say, "I used an event-driven architecture here because—", before you finish the sentence, they're already nodding, already pushing back, already making it better.&lt;br&gt;
That conversation. That specific aliveness of two minds on the same problem.&lt;/p&gt;

&lt;p&gt;You haven't had it in months. Maybe years.&lt;/p&gt;

&lt;p&gt;You've had good conversations. Friendly conversations. But not that conversation. The one where you leave feeling like your brain grew.&lt;/p&gt;

&lt;p&gt;So instead you go to Twitter/X. You go to dev.to. You go to Hacker News and read comment threads at 11pm not because you're avoiding sleep but because you're desperately, quietly, trying to feel less alone in the thing you care most about.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What it does to you over time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It makes you doubt yourself in strange ways.&lt;/p&gt;

&lt;p&gt;Not the obvious "Am I good enough?" kind of doubt — though that's there too. But a subtler erosion. You start to wonder if the way you think about problems is normal. You have no reference point. No colleague who builds things like you do.&lt;/p&gt;

&lt;p&gt;You over-engineer sometimes, chasing elegance that nobody will ever see. You under-engineer other times, cutting corners you're ashamed of because there's no one to hold the standard with you.&lt;/p&gt;

&lt;p&gt;You develop strange habits. Talking to rubber ducks. Writing READMEs with the exhaustive detail of someone who fears being misunderstood. &lt;/p&gt;

&lt;p&gt;Commenting code is not for the next developer but as a kind of message in a bottle, proof that you were here, that you thought carefully, and that you weren't just someone who kept the lights on.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;But here's the thing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are thousands of us.&lt;/p&gt;

&lt;p&gt;In insurance companies and law firms and small NGOs and family businesses and local councils and restaurants that needed an app. Everywhere that the world runs on software but doesn't quite run as software yet.&lt;/p&gt;

&lt;p&gt;We are the quiet layer. The ones who translated "we need a thing" into a thing, alone, and then went home without anyone to debrief with.&lt;/p&gt;

&lt;p&gt;And for some reason—maybe because dev culture is so focused on startups and big tech and 10x teams and Silicon Valley origin stories—nobody writes about us.&lt;/p&gt;

&lt;p&gt;Our loneliness doesn't get a conference talk. Our specific exhaustion doesn't get a Medium think piece. Our wins are invisible because there's no one internally who understands what winning even looked like.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;To the only dev in the room&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your instinct to Google things isn't weakness. It's what self-reliance looks like in practice.&lt;/p&gt;

&lt;p&gt;The code you wrote alone, that nobody reviewed, that's been held up in production for two years — that counts. That's real. That took courage even if nobody clapped.&lt;/p&gt;

&lt;p&gt;The meetings you sat through smiling, translating the untranslatable — that's emotional labour nobody put on your job description, and you've been doing it quietly without credit.&lt;/p&gt;

&lt;p&gt;And the longing you feel for a peer, for someone who gets it — that's not neediness. That's just being human and loving something deeply enough to want to share it.&lt;/p&gt;




&lt;p&gt;Are you (or were you) the only dev at your company? Tell me what it was actually like in the comments. I promise I'll read every single one.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>devjournal</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Something the internet doesn't say enough</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sun, 08 Mar 2026 14:28:54 +0000</pubDate>
      <link>https://dev.to/_boweii/something-the-internet-doesnt-say-enough-530l</link>
      <guid>https://dev.to/_boweii/something-the-internet-doesnt-say-enough-530l</guid>
      <description>&lt;p&gt;Some of the most brilliant, generous, and genuinely inspiring people I've come across in this dev community are women.&lt;/p&gt;

&lt;p&gt;@anjelica_, Jess Lee. Sylwia Laskowska, Sandra. Warda. And honestly — too many to name.&lt;/p&gt;

&lt;p&gt;We've never met in person. But a comment here, a post there, a like at the right moment — it counts more than you think.&lt;/p&gt;

&lt;p&gt;And offline? My mum taught me resilience before I even knew the word. My sisters keep me grounded. My girlfriend makes the hard days lighter.&lt;/p&gt;

&lt;p&gt;Happy International Women's Day to every woman building, writing, mentoring, debugging, and showing up.&lt;/p&gt;

&lt;p&gt;You are not background characters in this story. You're the plot. 🌍💙&lt;/p&gt;

</description>
      <category>womenintech</category>
      <category>devto</category>
      <category>community</category>
      <category>wecoded</category>
    </item>
  </channel>
</rss>
