<?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: Geon George</title>
    <description>The latest articles on DEV Community by Geon George (@geongeorge).</description>
    <link>https://dev.to/geongeorge</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F216916%2Fad19b488-f16b-4059-bfc4-5a40f5491748.png</url>
      <title>DEV Community: Geon George</title>
      <link>https://dev.to/geongeorge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/geongeorge"/>
    <language>en</language>
    <item>
      <title>Your Software Can't Heal What It Can't See: Support Is the Missing Loop</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Sat, 31 Oct 2026 20:27:10 +0000</pubDate>
      <link>https://dev.to/geongeorge/your-software-cant-heal-what-it-cant-see-support-is-the-missing-loop-h71</link>
      <guid>https://dev.to/geongeorge/your-software-cant-heal-what-it-cant-see-support-is-the-missing-loop-h71</guid>
      <description>&lt;p&gt;Picture a customer writing in to your SaaS product: "new leads stopped showing up in our CRM." Your dashboards are green. No exceptions in error tracking, no 5xx spike, p95 latency flat. By every machine-readable measure the app is healthy.&lt;/p&gt;

&lt;p&gt;It isn't. It's broken for that one customer, and the only sensor that noticed is a human typing into a chat widget. If you run support for any software product, you've had some version of this ticket.&lt;/p&gt;

&lt;p&gt;I build Superrelay, a support workspace for software teams, so I'm biased. But I've come to believe this: &lt;strong&gt;most "self-healing software" talk skips the part where most bugs actually show up.&lt;/strong&gt; The loop is missing its best sensor, and that sensor is your support inbox.&lt;/p&gt;

&lt;p&gt;This post covers what self-healing software actually is, where it goes blind, and how to wire support into the loop without letting an AI agent loose on production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "self-healing" means today
&lt;/h2&gt;

&lt;p&gt;The idea is old. IBM's &lt;a href="https://doi.org/10.1109/MC.2003.1160055" rel="noopener noreferrer"&gt;autonomic computing vision&lt;/a&gt; (Kephart and Chess, 2003) listed &lt;em&gt;self-healing&lt;/em&gt; next to self-configuring, self-optimizing and self-protecting, and described a control loop: monitor, analyze, plan, execute. Strip away the vocabulary and every modern version is the same four steps.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpf6tpiqwj32v8t38pl2t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpf6tpiqwj32v8t38pl2t.png" alt="The classic self-healing loop: detect, diagnose, remediate, verify" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You probably already run parts of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Restart on failure.&lt;/strong&gt; A Kubernetes &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/probes/" rel="noopener noreferrer"&gt;liveness probe&lt;/a&gt; that fails past its threshold gets the container killed and restarted. Crude, and very effective against deadlocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop the bleeding.&lt;/strong&gt; A &lt;a href="https://martinfowler.com/bliki/CircuitBreaker.html" rel="noopener noreferrer"&gt;circuit breaker&lt;/a&gt; trips after repeated failures, fails fast, and can reset itself once the dependency recovers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn it off.&lt;/strong&gt; Long-lived ops toggles act as &lt;a href="https://martinfowler.com/articles/feature-toggles.html" rel="noopener noreferrer"&gt;kill switches&lt;/a&gt; that let operators degrade a non-critical feature without a deploy. Pete Hodgson notes they "could be seen as a manually-managed Circuit Breaker."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roll it back.&lt;/strong&gt; &lt;a href="https://argo-rollouts.readthedocs.io/en/latest/features/analysis/" rel="noopener noreferrer"&gt;Argo Rollouts analysis&lt;/a&gt; can query a success-rate metric during a canary and abort the rollout, sending traffic back to the stable version, when the metric fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the fix.&lt;/strong&gt; The newest layer. Sentry's &lt;a href="https://docs.sentry.io/product/ai-in-sentry/seer/autofix/" rel="noopener noreferrer"&gt;Seer Autofix&lt;/a&gt; does root-cause analysis on an issue, proposes a fix, and can be configured to open a PR. It can also hand off to a coding agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is a good idea. And every one of them depends on the same thing: &lt;strong&gt;the failure has to be visible to a machine.&lt;/strong&gt; A probe has to fail. An exception has to be thrown. A metric has to move.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs telemetry can't see
&lt;/h2&gt;

&lt;p&gt;Try this: open your last 50 support tickets and count how many have a matching error event. Whatever that number is, it's the real coverage of your self-healing loop. The rest reached you only because a customer took the time to write in.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F19y6jqa7ifvdyt0wq5oa.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F19y6jqa7ifvdyt0wq5oa.png" alt="The invisible-bug iceberg: telemetry sees exceptions and 5xx spikes; support tickets reveal desynced settings, environment conflicts, confusing UX, stale docs and silent integration failures" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what's under the waterline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State that's wrong for one account.&lt;/strong&gt; A setting saved in your database but never applied on the platform side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment conflicts.&lt;/strong&gt; A customer's browser extension, a corporate proxy, another vendor's script fighting yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Success" that isn't.&lt;/strong&gt; Plenty of third-party APIs answer &lt;code&gt;200&lt;/code&gt; when the operation didn't happen. Under the &lt;a href="https://graphql.github.io/graphql-over-http/draft/" rel="noopener noreferrer"&gt;GraphQL-over-HTTP spec draft&lt;/a&gt;, an error raised while executing an operation doesn't fail the request at the HTTP level: the response is still a &lt;code&gt;2xx&lt;/code&gt;, and the failure lives in an &lt;code&gt;errors&lt;/code&gt; array in the body. If you only check the status code, your logs say everything is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations that quietly stop.&lt;/strong&gt; An expired token, a webhook subscription that got dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusing UX.&lt;/strong&gt; Nothing is broken; the customer just can't find the button. That's still a defect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong docs.&lt;/strong&gt; The help article describes last month's UI. The code is fine; the product is still broken for anyone who reads it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these throw. All of them produce tickets. &lt;strong&gt;Support is the sensor network for the failures your telemetry can't see.&lt;/strong&gt; Most teams treat that sensor as a cost center instead of a signal source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the loop: support as a first-class sensor
&lt;/h2&gt;

&lt;p&gt;Here's the loop I think small teams should build. It has two speeds: fix &lt;em&gt;this customer&lt;/em&gt; now, and fix &lt;em&gt;the class of bug&lt;/em&gt; so the next ticket never arrives.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcbz85udju4m1qn39hkf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcbz85udju4m1qn39hkf.png" alt="The closed loop: ticket arrives, structure the signal, support agent fixes the instance with approval, coding agent fixes the class of bug, team reviews and ships, every reporter is notified" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Turn tickets into structured signals
&lt;/h3&gt;

&lt;p&gt;A raw ticket is like an unparsed log line. Before anything automated can use it, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tags&lt;/strong&gt; that mean something to engineering (&lt;code&gt;bug:settings-sync&lt;/code&gt;, not &lt;code&gt;urgent&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context attached automatically&lt;/strong&gt;: account ID, plan, app version, the page they were on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clustering&lt;/strong&gt;: five customers describing one bug in five different ways should become one issue with five reporters. This is where an LLM is genuinely useful. Grouping fuzzy natural-language reports is exactly what it's good at.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Let a support agent fix the instance, safely
&lt;/h3&gt;

&lt;p&gt;If your agent can only &lt;em&gt;talk&lt;/em&gt;, it can only deflect. The useful version has &lt;strong&gt;read access&lt;/strong&gt; to logs and account state, plus a short list of &lt;strong&gt;allowlisted actions&lt;/strong&gt; it can propose. You can expose those over &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; or a plain HTTPS endpoint.&lt;/p&gt;

&lt;p&gt;The MCP spec itself says there should always be a human in the loop who can deny tool invocations, and that clients should prompt for confirmation on sensitive operations. Take that seriously.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Let a coding agent fix the class of bug
&lt;/h3&gt;

&lt;p&gt;Once a cluster is tagged, it becomes a task: a coding agent reads the tickets (via CLI or MCP), reproduces the issue with a failing test, fixes it, and &lt;strong&gt;updates the docs in the same PR&lt;/strong&gt;. A human reviews and merges. Your existing CI, canaries and flags still apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tell the customer
&lt;/h3&gt;

&lt;p&gt;The step everyone forgets. When the fix ships, every reporter in the cluster gets a message with a link. That's what turns "this app is buggy" into "these people actually fix things."&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: an integration that silently desyncs for one account
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This is an illustrative example, simplified from a common class of SaaS integration bug. The code is generic, not any product's real API.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setup.&lt;/strong&gt; A SaaS app lets each customer workspace turn on "Sync new leads to CRM." The app stores &lt;code&gt;crmSyncEnabled&lt;/code&gt; in its own database and registers a webhook subscription in the customer's CRM through the CRM's GraphQL API. Admins toggle the setting in the app's settings page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug.&lt;/strong&gt; The CRM provider recently tightened its rate limits. During a bulk settings save, the subscription call gets rate-limited, and the GraphQL response is a &lt;code&gt;200&lt;/code&gt; with a &lt;code&gt;RATE_LIMITED&lt;/code&gt; error in the body. The code only checks &lt;code&gt;res.ok&lt;/code&gt;, so the database says &lt;code&gt;true&lt;/code&gt; and the CRM has no subscription. No exception, no alert.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ticket.&lt;/strong&gt; "New leads stopped showing up in our CRM."&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr1zeppr9exudeedlangm.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr1zeppr9exudeedlangm.png" alt="Before and after timeline of the same bug: without the loop it ends with a manual DB patch and stale docs; with the loop it ends with a merged fix and every reporter notified" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instance fix.&lt;/strong&gt; The support agent calls a read-only tool that compares app state with the CRM-side state, then proposes a scoped write. Here's a sketch of both tools with the MCP TypeScript SDK:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&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="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;z&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="s2"&gt;zod&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;server&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;McpServer&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="s2"&gt;sync-app-support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Read-only: safe to call freely. Returns evidence the agent can cite.&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;diff_account_settings&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Compare app settings with the account's CRM integration&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Read-only. Shows where our DB and the CRM-side subscription disagree.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accountId&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;diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;diffSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// your code&lt;/span&gt;
    &lt;span class="k"&gt;return&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="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="c1"&gt;// Write: one account per call, re-applies existing DB values only, audit-logged.&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resync_account_settings&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Re-apply saved settings to one account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Re-registers the CRM subscription from DB values. Requires human approval.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;destructiveHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;idempotentHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ticketId&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;assertApproved&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resync_account_settings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// human said yes&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`resync:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&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="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;perHours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resyncSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// must check the GraphQL errors array&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&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="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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;Notice what the write tool &lt;em&gt;can't&lt;/em&gt; do: touch more than one account, take arbitrary values, or run without an approval tied to a ticket. One more thing: the spec warns clients to treat annotations as untrusted unless they come from a trusted server. They're hints, not enforcement. The enforcement lives in your handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Class fix.&lt;/strong&gt; Two more customers report the same thing that week, and the cluster gets tagged &lt;code&gt;bug:settings-sync&lt;/code&gt;. That tag fires a webhook that turns the cluster into an agent-ready GitHub issue:&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;// POST /hooks/support-tagged — called by your help desk when a ticket is tagged.&lt;/span&gt;
&lt;span class="c1"&gt;// The payload shape here is hypothetical; adapt it to your help desk's webhooks.&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;Octokit&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="s2"&gt;@octokit/rest&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;gh&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;Octokit&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;GITHUB_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;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bad signature&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="p"&gt;}&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bug:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ignored&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;`**Cluster:** &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cluster&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="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; reports)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`**Accounts:** &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&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;t&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&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="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&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="s2"&gt;`**Summary:** &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&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="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;### Agent instructions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1. Read the linked tickets via the support CLI/MCP.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2. Write a failing test that reproduces the report.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3. Fix it, then update any help article that describes this behavior.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4. Open a PR. Do not merge.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&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;t&lt;/span&gt;&lt;span class="p"&gt;)&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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="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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;gh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&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;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acme&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync-app&lt;/span&gt;&lt;span class="dl"&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="s2"&gt;`[support] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cluster&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;labels&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="s2"&gt;from-support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent-task&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point whichever coding agent you use at &lt;code&gt;agent-task&lt;/code&gt; issues. In this example, the PR it opens adds a test with a mocked &lt;code&gt;RATE_LIMITED&lt;/code&gt; response, makes the subscription writer check the &lt;code&gt;errors&lt;/code&gt; array and retry with backoff, and edits the help article that claimed changes "apply instantly." A human merges it. The three reporters get a "fixed, here's what happened" message.&lt;/p&gt;

&lt;p&gt;The detection didn't come from telemetry. Every step &lt;em&gt;after&lt;/em&gt; detection looks like the self-healing loop you already know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails (non-negotiable)
&lt;/h2&gt;

&lt;p&gt;Putting an agent between customers and production means designing for the day it's wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read first, write rarely.&lt;/strong&gt; Most of the value comes from read-only tools that pull account state and logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask before acting.&lt;/strong&gt; Every write needs an explicit human or customer confirmation tied to a ticket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cite sources.&lt;/strong&gt; An answer or diagnosis should link the article, log line or diff it came from. If it can't cite, it should say so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit the blast radius.&lt;/strong&gt; One account per call, fixed parameters, rate limits, no bulk operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit everything.&lt;/strong&gt; Who approved what, when, with what result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Humans merge.&lt;/strong&gt; Coding agents open PRs; they don't push to main or deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped keys.&lt;/strong&gt; The agent's credentials should only see the tools you intend it to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Superrelay fits
&lt;/h2&gt;

&lt;p&gt;Full disclosure, this is the problem I'm building for. We built &lt;a href="https://superrelay.com" rel="noopener noreferrer"&gt;Superrelay&lt;/a&gt; to run support for our own software products, and it's shaped around this loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One inbox for every product&lt;/strong&gt;, across chat, email, Slack and in-app, with product-specific knowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An AI agent that cites the article it answered from&lt;/strong&gt; and can start in draft or supervised mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product actions&lt;/strong&gt; through an HTTPS endpoint you register. You choose which actions exist and which need customer confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs as code&lt;/strong&gt;: help-center articles live as Markdown in your GitHub repo. Merging publishes them, and pull requests that change documented behavior get flagged with the affected articles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CLI and MCP server&lt;/strong&gt;, so Claude Code, Cursor, Codex and friends can read conversations (&lt;code&gt;relay list --limit 20 --json&lt;/code&gt;) and group recurring issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priced by products and seats, never per ticket&lt;/strong&gt;, because charging more when your product breaks more is a strange incentive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be clear about the limits: Superrelay doesn't autonomously repair or deploy your code. It gives your agents and your team the customer context and safe actions; you connect the steps and review the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist you can apply this week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit 50 tickets.&lt;/strong&gt; How many had a matching error event? That gap is your blind spot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create five engineering tags&lt;/strong&gt; for your most common ticket types and use them consistently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach context automatically&lt;/strong&gt;: account ID, plan, app version, current page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write one read-only diagnostic tool&lt;/strong&gt;, like &lt;code&gt;diff_account_state&lt;/code&gt;, that support (human or AI) can run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write one safe, scoped, idempotent write action&lt;/strong&gt; with approval and an audit log.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check every "success" path.&lt;/strong&gt; Grep for places you trust a &lt;code&gt;200&lt;/code&gt; from a GraphQL API without checking &lt;code&gt;errors&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipe tagged clusters into GitHub issues&lt;/strong&gt; with reproduction instructions for your coding agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put docs in the PR template&lt;/strong&gt;: "Which help article does this change?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Close the loop&lt;/strong&gt;: notify every reporter when the fix ships.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Self-healing software isn't a smarter pod restart. It's a feedback loop, and the most honest signal in it is a customer telling you something's wrong. Wire that in, and your product starts fixing the bugs your dashboards never saw.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're building this loop yourself, I'd love to hear how you're doing it in the comments. If you'd rather not build the support half from scratch, &lt;a href="https://superrelay.com/pricing" rel="noopener noreferrer"&gt;Superrelay has a free plan&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Canvas-Txt: Multiline Text Magic on HTML5 Canvas</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Fri, 03 Nov 2023 17:55:06 +0000</pubDate>
      <link>https://dev.to/geongeorge/canvas-txt-multiline-text-magic-on-html5-canvas-41b8</link>
      <guid>https://dev.to/geongeorge/canvas-txt-multiline-text-magic-on-html5-canvas-41b8</guid>
      <description>&lt;p&gt;As developers, we've often been in situations where we need to render text on a canvas element in HTML5. It could be for a game, a custom chart, or even an advanced animation. But, anyone who's tried this knows that the native canvas API is... a bit limited when it comes to handling text. Enter &lt;strong&gt;Canvas-Txt&lt;/strong&gt; 📐✨, a minuscule, dependency-free library that transforms how we draw text on HTML5 canvas.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Canvas-txt?
&lt;/h2&gt;

&lt;p&gt;Canvas-Txt extends the native canvas text rendering capabilities with a set of much-needed features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiline Text&lt;/strong&gt;: Easily render paragraphs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Line Breaks&lt;/strong&gt;: No more manual calculation for breaking lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Alignment&lt;/strong&gt;: Supports horizontal, vertical, and even justify alignment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging Tools&lt;/strong&gt;: Comes with visual aids to help place your text exactly where you want it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: It's built for speed, so no more trade-offs between functionality and performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zxLw3qZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ld423ipfbxh26i07flh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zxLw3qZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ld423ipfbxh26i07flh.png" alt="Image description" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  See It in Action
&lt;/h2&gt;

&lt;p&gt;Before we dive into the code, take a peek at the live demo &lt;a href="https://canvas-txt.geongeorge.com"&gt;here&lt;/a&gt; to see what you'll be capable of after this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;p&gt;First things first, let's add Canvas-Txt to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add canvas-txt

&lt;span class="c"&gt;# Or with npm&lt;/span&gt;
npm i canvas-txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Basics: Drawing Text
&lt;/h2&gt;

&lt;p&gt;Here's the simplest example to get you started. Add a canvas element to your HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myCanvas"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's draw some text on it using Canvas-Txt:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drawText&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;canvas-txt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myCanvas&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;ctx&lt;/span&gt; &lt;span class="o"&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;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearRect&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&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;txt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lorem ipsum dolor sit amet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;drawText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Total height = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;height&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run this code, you’ll see "Lorem ipsum dolor sit amet" beautifully rendered on the canvas, enclosed in a 200x200px box starting from position (100,200).&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/geon-george/embed/MWLbwVm?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Canvas-Txt on the Server with Node.js
&lt;/h2&gt;

&lt;p&gt;If you're working in a Node.js environment, you can use Canvas-Txt with node-canvas:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createCanvas&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drawText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas-txt&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&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;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&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;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&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;txt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;drawText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&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;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Total height = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;height&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="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, you can generate images on the server side that include dynamic text content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And there you have it! Canvas-Txt is a powerful and easy-to-use solution for all your canvas text rendering needs. Whether you’re working on a dynamic web application or generating images on the server side, Canvas-Txt simplifies the process, giving you the ability to focus on creating a great user experience rather than wrestling with text metrics.&lt;/p&gt;

&lt;p&gt;To dive even deeper and to learn more about the customization options available, be sure to check out &lt;a href="https://github.com/geongeorge/Canvas-Txt"&gt;the official Canvas-Txt documentation&lt;/a&gt;. With this tool in your development arsenal, you're well-equipped to make your canvas text renderings as simple or sophisticated as your project requires.&lt;/p&gt;

&lt;p&gt;Happy coding, and may your text always align perfectly on your canvas!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Multiline Text in HTML Canvas</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Fri, 14 Jul 2023 17:53:42 +0000</pubDate>
      <link>https://dev.to/geongeorge/multiline-text-in-html-canvas-434h</link>
      <guid>https://dev.to/geongeorge/multiline-text-in-html-canvas-434h</guid>
      <description>&lt;p&gt;I've recently given a facelift to Canvas-Txt, a project that I initially launched some moons ago. For those not in the know, Canvas-Txt is a teensy-weensy library that does one job, and one job only - render multiline text on your HTML5 Canvas, without hanging on the coattails of any dependencies.&lt;/p&gt;

&lt;p&gt;Now, let me show you&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/geongeorge/Canvas-Txt"&gt;https://github.com/geongeorge/Canvas-Txt&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;To give you an idea, let me walk you through a simple use-case scenario:&lt;/p&gt;

&lt;p&gt;First, install Canvas Txt either via yarn or npm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add canvas-txt

or

npm i canvas-txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myCanvas"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drawText&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;canvas-txt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myCanvas&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;ctx&lt;/span&gt; &lt;span class="o"&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;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clearRect&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&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;txt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lorem ipsum dolor sit amet&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;drawText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Total height = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;height&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe src="https://jsfiddle.net/geongeorgek/n5xw3ufj/2//embedded//dark" width="100%" height="600"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And there you have it. Your multiline text, neatly rendered on an HTML canvas.&lt;/p&gt;

&lt;p&gt;What's even cooler is that you can do this in Node too! Just have a look at the Node js demo here &lt;a href="https://github.com/geongeorge/Canvas-Txt/blob/master/src/node-test.ts"&gt;https://github.com/geongeorge/Canvas-Txt/blob/master/src/node-test.ts&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summing Up
&lt;/h2&gt;

&lt;p&gt;That's pretty much it. With Canvas Txt v4, I've aimed to simplify your journey of handling multiline text in HTML canvas.&lt;/p&gt;

&lt;p&gt;Whether you're just dabbling in canvas or are an experienced pro, I hope you'll find Canvas Txt useful.&lt;/p&gt;

&lt;p&gt;As always, if you have any suggestions, comments, or just want to give a shout out, hit me up on my Twitter!&lt;/p&gt;

</description>
      <category>html</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>My open-source project hit 2k stars</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Thu, 02 Jul 2020 13:00:23 +0000</pubDate>
      <link>https://dev.to/geongeorge/my-open-source-project-hit-2k-stars-1moa</link>
      <guid>https://dev.to/geongeorge/my-open-source-project-hit-2k-stars-1moa</guid>
      <description>&lt;p&gt;Okay, before you go ahead and look: &lt;strong&gt;I lied&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me explain :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://iHateRegex.io/"&gt;https://iHateRegex.io/&lt;/a&gt; is a project I launched in February this year.&lt;br&gt;
It is basically a regular expression cheat sheet with neat visualizations and explanations for common expressions.&lt;/p&gt;

&lt;p&gt;As of writing this article, it has &lt;strong&gt;1,987&lt;/strong&gt; stars on GitHub. And it is only a matter of a few hours to reach 2,000:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/geongeorge/i-hate-regex/"&gt;https://github.com/geongeorge/i-hate-regex/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my defense, Github did round that number to 2k anyway.&lt;/p&gt;

&lt;p&gt;I'm super stoked about this🔥.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But that is not the only reason why I'm writing this article.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8X_LgD6C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8zzeqnhzibjddfykklm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8X_LgD6C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8zzeqnhzibjddfykklm5.png" alt="ihateregex" width="880" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Sorry about the long content. I didn't want to add gifs or unwanted images here)&lt;/p&gt;

&lt;h1&gt;
  
  
  Some Context
&lt;/h1&gt;

&lt;p&gt;I started working on this in August 2019. This took around half a year to complete. &lt;/p&gt;

&lt;p&gt;Now, this might look like a very simple application from outside and I'm not arguing. It is in fact very easy to create.&lt;/p&gt;

&lt;p&gt;At the time I had no idea what I was creating. I had no clue what I was aiming for. I had college in the morning and I worked as a freelance developer at night. Also had a few products for which I provided support.&lt;/p&gt;

&lt;p&gt;And here I was with a domain - &lt;strong&gt;ihateregex.io&lt;/strong&gt; hoping to make a regular expression cheat sheet with.&lt;/p&gt;

&lt;p&gt;To be honest, I didn't even know what I should include, how to present this, and had 100s of questions like this.&lt;/p&gt;

&lt;p&gt;Halfway through making this I remember writing this in my blog:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I spend a lot of time on this. But as I was nearing the launch, my old pal Imposter Syndrome kicked in. I felt it wasn’t worth my effort and that I’m gonna be made fun of because of how useless it is. - &lt;a href="https://geongeorge.com/2019-look-back/"&gt;from my blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I felt like an imposter, I'm not a regular expression expert myself, People would make fun of how stupid the domain was.&lt;/p&gt;

&lt;p&gt;One month had passed after writing the above blog.&lt;/p&gt;

&lt;p&gt;One day, mustering up some courage I just decided to release this on product hunt.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Launch
&lt;/h1&gt;

&lt;p&gt;Here I was, hunting my first product in product hunt. I asked a few friends for advice and decided to press the submit button. I shared this with a few friends and told them to try this product.&lt;/p&gt;

&lt;p&gt;PEOPLE LOVED IT!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I was getting upvotes left and right.&lt;/li&gt;
&lt;li&gt;This was getting shared everywhere. &lt;/li&gt;
&lt;li&gt;I posted this on hacker news and I woke up the next day to see it's already &lt;strong&gt;#3 on hacker news.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;This ended up becoming the number &lt;strong&gt;#2 Product of the day&lt;/strong&gt; on product hunt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nA-8UhPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vonqw04wizv0uffsc9wy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nA-8UhPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vonqw04wizv0uffsc9wy.jpeg" alt="ihateregex #3 on hackernews" width="880" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I shared this on &lt;strong&gt;Dev.to&lt;/strong&gt; and people started buying me coffee and got lots of amazing feedback :)&lt;/p&gt;

&lt;p&gt;I even got sponsorship from the amazing people at &lt;a href="https://www.datree.io/"&gt;Datree&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Today
&lt;/h1&gt;

&lt;p&gt;Skip to today and the product has (almost) 2k stars on Github.&lt;br&gt;
People are submitting expressions, many have bookmarked this as their goto regex tool.&lt;/p&gt;

&lt;p&gt;If you have something in your mind just make it. Give to the world the things you make and you never know what will happen.&lt;br&gt;
The problem (I think) is we all try to predict the future. With all our biases and fears we end up choosing the path more traveled. the safe path. And if that is a good or bad choice is up to you.&lt;/p&gt;

&lt;p&gt;Make sure to give me a follow on twitter. &lt;/p&gt;

&lt;p&gt;I tweet about my journey as a maker: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/geongeorgek/"&gt;https://twitter.com/geongeorgek/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading :)&lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>vue</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Lazy Load Images in 2 seconds</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Fri, 05 Jun 2020 12:09:33 +0000</pubDate>
      <link>https://dev.to/geongeorge/lazy-load-all-images-in-2-seconds-2ppf</link>
      <guid>https://dev.to/geongeorge/lazy-load-all-images-in-2-seconds-2ppf</guid>
      <description>&lt;h2&gt;
  
  
  The Story
&lt;/h2&gt;

&lt;p&gt;I have this internal tool for some purpose. Now, this has a page where it displays like 50+ images grid in smaller sizes.&lt;br&gt;
The page always load very slow and I hated it.&lt;/p&gt;

&lt;p&gt;So We can solve this by &lt;strong&gt;properly generating smaller thumbnails&lt;/strong&gt; of the images and/or &lt;strong&gt;lazy loading&lt;/strong&gt; the images.&lt;/p&gt;

&lt;p&gt;This isn't really isn't used by anyone else other than me. So I decided to skip the thumbnail part and add 2 lines of js to lazy load the hack out of these stupid images.&lt;/p&gt;

&lt;p&gt;Then I stumbled upon something I completely forgot that existed.&lt;/p&gt;
&lt;h2&gt;
  
  
  loading="lazy"
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's pretty much everything you need to make your images lazy load.&lt;/p&gt;

&lt;p&gt;Now this is an HTML5 Feature and probably might not be supported by every browser out there.&lt;br&gt;
It might not bring fancy animations and fade transitions onto the table&lt;/p&gt;

&lt;p&gt;But IT WORKS!!&lt;/p&gt;

&lt;p&gt;The previous webpage is atleast 3 times as fast.&lt;/p&gt;

&lt;p&gt;Read more here: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#:~:text=On%20this%20Page&amp;amp;text=Lazy%20loading%20is%20a%20strategy,into%20reduced%20page%20load%20times."&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Use the &lt;code&gt;loading&lt;/code&gt; attribute in HTML&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update
&lt;/h2&gt;

&lt;p&gt;You definitely should not use it in anything important.&lt;br&gt;
You need a polyfill to get it work on all browsers.&lt;br&gt;
Read my story and see why I used it.&lt;/p&gt;

</description>
      <category>html</category>
      <category>javascript</category>
      <category>css</category>
    </item>
    <item>
      <title>I Made an NPM Package to Render text on HTML5 Canvas</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Sun, 26 Apr 2020 05:09:25 +0000</pubDate>
      <link>https://dev.to/geongeorge/i-made-an-npm-package-to-render-text-on-html5-canvas-46p0</link>
      <guid>https://dev.to/geongeorge/i-made-an-npm-package-to-render-text-on-html5-canvas-46p0</guid>
      <description>&lt;p&gt;Rendering text onto HTML5 Canvases has always been a hassle for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;There was no proper way to align text, to wrap words if it overflows and even to properly position text without knowing the contents of the text.&lt;/p&gt;

&lt;p&gt;Larger packages had this feature but I didn't wanna import a whole canvas library just to render some text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U_Dl331B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/TvGaHZu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U_Dl331B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/TvGaHZu.png" alt="" width="880" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Canvas-Txt
&lt;/h2&gt;

&lt;p&gt;I made a library called &lt;strong&gt;Canvas-Txt&lt;/strong&gt;, which will let you render textboxes to html canvases and has all sorts of aligning and multi-line features&lt;/p&gt;

&lt;p&gt;I created this for myself and it grew with some community help. This package was released to NPM and has some weekly downloads now.&lt;/p&gt;

&lt;p&gt;I added some major changes and just released V2 of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiline text&lt;/li&gt;
&lt;li&gt;Auto line breaks&lt;/li&gt;
&lt;li&gt;Horizontal Align&lt;/li&gt;
&lt;li&gt;Vertical Align&lt;/li&gt;
&lt;li&gt;Easy Debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gUqYBUGS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/qV2x2zV.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gUqYBUGS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/qV2x2zV.jpg" alt="" width="807" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  URLs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Github:&lt;/strong&gt;  &lt;a href="https://github.com/geongeorge/canvas-txt"&gt;https://github.com/geongeorge/canvas-txt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo:&lt;/strong&gt;  &lt;a href="http://canvas-txt.geongeorge.com/"&gt;http://canvas-txt.geongeorge.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>npm</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>Looking for contributors for iHateRegex.io &lt;3</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Sat, 01 Feb 2020 17:08:13 +0000</pubDate>
      <link>https://dev.to/geongeorge/looking-for-contributors-for-ihateregex-io-3-5goa</link>
      <guid>https://dev.to/geongeorge/looking-for-contributors-for-ihateregex-io-3-5goa</guid>
      <description>&lt;p&gt;This community made me to launch iHateRegex.io in product hunt and hacker news. 🙏&lt;/p&gt;

&lt;p&gt;it turned out really well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;topped a lot of subreddits.&lt;/li&gt;
&lt;li&gt;#3 on hacker news 😮&lt;/li&gt;
&lt;li&gt;#2 on product hunt 😍&lt;/li&gt;
&lt;li&gt;a lot of retweets on Twitter&lt;/li&gt;
&lt;li&gt;100s of bookmarks&lt;/li&gt;
&lt;/ul&gt;


&lt;center&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T6RKwBXe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DqeCEWT.jpg" width="880" height="417"&gt;&lt;/center&gt;

&lt;p&gt;I did a lot of updates in the last few hours.&lt;/p&gt;

&lt;p&gt;It seems to be gaining small traction on GitHub as well,&lt;/p&gt;

&lt;p&gt;I'm inviting anybody who would like to contribute to this amazing project:&lt;br&gt;
😻👉&lt;br&gt;
&lt;a href="https://github.com/geongeorge/i-hate-regex/"&gt;https://github.com/geongeorge/i-hate-regex/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is build using Nuxt.js (based on Vue.js)&lt;br&gt;
It runs entirely on Javascript (Server-side Rendering)&lt;/p&gt;



&lt;center&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5REvuHa_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/iE146mv.png" width="880" height="452"&gt;&lt;/center&gt;

</description>
      <category>javascript</category>
      <category>productivity</category>
      <category>github</category>
      <category>git</category>
    </item>
    <item>
      <title>Launched iHateregex.io in Producthunt</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Fri, 31 Jan 2020 09:00:16 +0000</pubDate>
      <link>https://dev.to/geongeorge/launched-ihateregex-io-in-producthunt-13l4</link>
      <guid>https://dev.to/geongeorge/launched-ihateregex-io-in-producthunt-13l4</guid>
      <description>&lt;p&gt;I just launched iHateRegex on product hunt:&lt;br&gt;
👉👉&lt;br&gt;
&lt;a href="https://www.producthunt.com/posts/i-hate-regex"&gt;https://www.producthunt.com/posts/i-hate-regex&lt;/a&gt;&lt;br&gt;
❤&lt;br&gt;
Thanks to everyone in this community who motivated me to do this&lt;/p&gt;

&lt;p&gt;See my previous post about the tool: &lt;a href="https://dev.to/geongeorge/i-hated-regex-so-much-that-i-made-ihateregex-io-1ih4"&gt;https://dev.to/geongeorge/i-hated-regex-so-much-that-i-made-ihateregex-io-1ih4&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>regex</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I hated Regex so much that I made iHateRegex.io</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Fri, 31 Jan 2020 04:10:40 +0000</pubDate>
      <link>https://dev.to/geongeorge/i-hated-regex-so-much-that-i-made-ihateregex-io-1ih4</link>
      <guid>https://dev.to/geongeorge/i-hated-regex-so-much-that-i-made-ihateregex-io-1ih4</guid>
      <description>&lt;p&gt;In years of being a developer I never really spent the time to learn regular expressions. It always seemed like a hard nut to crack.&lt;/p&gt;

&lt;center&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FV4dxfUs.png"&gt;&lt;/center&gt;

&lt;p&gt;I used to spend so much time trying to find regular expressions for my use case.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;now you have two problems&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Enter iHateRegex.io
&lt;/h2&gt;

&lt;center&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FPwNaWOk.png"&gt;&lt;/center&gt;

&lt;p&gt;&lt;a href="https://iHateRegex.io" rel="noopener noreferrer"&gt;https://iHateRegex.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I made a simple tool that will explain to you commonly used regular expressions work.&lt;/p&gt;

&lt;p&gt;It is a simple tool that will show a visual graph as well as matches and highlighting for code.&lt;/p&gt;

&lt;p&gt;Hope you like the tool :D &lt;/p&gt;

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

&lt;p&gt;Public Repo: &lt;a href="https://github.com/geongeorge/i-hate-regex" rel="noopener noreferrer"&gt;https://github.com/geongeorge/i-hate-regex&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The application is built using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The awesome Nuxt.js framework&lt;/li&gt;
&lt;li&gt;Tailwind.css&lt;/li&gt;
&lt;li&gt;for the visual demo I used: &lt;a href="https://gitlab.com/javallone/regexper-static" rel="noopener noreferrer"&gt;https://gitlab.com/javallone/regexper-static&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Thank you for all the love and support guys 💓 
Posting this has given me enough motivation to work on this more.&lt;/li&gt;
&lt;li&gt;The application is just an MVP right now. With the support of everyone, I can make this into a great project for beginners.&lt;/li&gt;
&lt;li&gt;I'm also working on tutorial pages to get started on regex. &lt;a href="https://imgur.com/a/giEWl0j" rel="noopener noreferrer"&gt;here's a sample&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Producthunt launch
&lt;/h3&gt;

&lt;p&gt;I just launched iHateRegex on &lt;em&gt;Producthunt&lt;/em&gt; 😺&lt;/p&gt;

&lt;p&gt;👉👉&lt;br&gt;
&lt;a href="https://www.producthunt.com/posts/i-hate-regex" rel="noopener noreferrer"&gt;https://www.producthunt.com/posts/i-hate-regex&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F8FKfOeO.png"&gt;&lt;/center&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>computerscience</category>
      <category>regex</category>
    </item>
    <item>
      <title>Building a regex learn-by-examples web application</title>
      <dc:creator>Geon George</dc:creator>
      <pubDate>Thu, 29 Aug 2019 01:12:24 +0000</pubDate>
      <link>https://dev.to/geongeorge/building-a-regex-learn-by-examples-web-application-15k2</link>
      <guid>https://dev.to/geongeorge/building-a-regex-learn-by-examples-web-application-15k2</guid>
      <description>&lt;p&gt;I'm building a regex "commonly used" library which includes an explanation for each of the expressions. &lt;/p&gt;

&lt;p&gt;I did not find a good alternative other than this ancient-looking website: &lt;a href="http://regexlib.com/"&gt;http://regexlib.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dumb me did not realize until the halfway completion of the application that there are multiple flavors for regex.&lt;br&gt;
I can only show examples and process js expressions. Should I ditch the idea? or complete it even if it's shitty?&lt;/p&gt;

</description>
      <category>regex</category>
      <category>javascript</category>
      <category>php</category>
    </item>
  </channel>
</rss>
