<?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: GraceSoft</title>
    <description>The latest articles on DEV Community by GraceSoft (@gracesoftdev).</description>
    <link>https://dev.to/gracesoftdev</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%2F3861707%2F4bb88ba3-4872-49c8-86bd-016d6ca57ebf.png</url>
      <title>DEV Community: GraceSoft</title>
      <link>https://dev.to/gracesoftdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gracesoftdev"/>
    <language>en</language>
    <item>
      <title>Snap a Dish, Get a Recipe: Building GraceSoft DishLens Solo</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Wed, 05 Aug 2026 06:15:56 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/snap-a-dish-get-a-recipe-building-gracesoft-dishlens-solo-5d36</link>
      <guid>https://dev.to/gracesoftdev/snap-a-dish-get-a-recipe-building-gracesoft-dishlens-solo-5d36</guid>
      <description>&lt;p&gt;This week the recording of GraceSoft CMS's "vibe coding" series is on pause. Something more exciting pulled my attention: my first venture into mobile apps, via React Native.&lt;/p&gt;

&lt;p&gt;The pitch is simple. Snap a photo of a dish, and get back a recipe you could actually cook at home, plus its nutritional breakdown. Building it has been anything but simple — but that's the part worth writing about.&lt;/p&gt;

&lt;p&gt;I'm building both the API and the mobile app myself. This is a new domain for me, still a one-woman show, and I leaned on Claude Code to move fast. But moving fast isn't the hard part of a project like this — knowing which edge cases matter, what to log, and how to structure a pipeline so it doesn't quietly drift is. That's domain knowledge and dev instinct, and no tool hands that to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the system
&lt;/h2&gt;

&lt;p&gt;Right now everything lives in one monorepo (pnpm + Turborepo), with two apps and a handful of shared packages (&lt;code&gt;shared-db&lt;/code&gt; for Prisma/Postgres, &lt;code&gt;shared-config&lt;/code&gt; for env validation, &lt;code&gt;shared-logger&lt;/code&gt; for structured logging).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DriveSync&lt;/strong&gt; is the backend service that keeps a Pinecone vector database in sync with recipes stored as Google Docs in a Drive folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DishLens&lt;/strong&gt; is the API that takes a single-dish photo and returns a "homable" recipe and its nutrition info. It's paired with a React Native (Expo) app that's the actual user-facing product.&lt;/p&gt;

&lt;p&gt;DriveSync is done. DishLens is still in bug-fixing mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  DriveSync: keeping a vector DB honest
&lt;/h2&gt;

&lt;p&gt;The job sounds simple — watch a Drive folder, sync new or changed docs into Pinecone. The details are where it got interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change detection&lt;/strong&gt; against Google Drive, so I'm not re-processing documents that haven't changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction pipeline&lt;/strong&gt; that pulls text out of Docs, Sheets, Slides, and PDFs — each with different structure and failure modes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token-budgeted chunking with overlap&lt;/strong&gt;, so embeddings stay within model limits without cutting a recipe's context in half.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batched embedding generation&lt;/strong&gt; via OpenAI, with retries, because batch jobs against a third-party API will fail partway through eventually — plan for it up front.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedup and stale-vector deletion&lt;/strong&gt;, so a recipe that's edited or removed from Drive doesn't leave orphaned, outdated vectors haunting search results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postgres-backed sync state&lt;/strong&gt;, plus job locking, so overlapping sync runs don't stomp on each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: sync logs, failure alerts, and status/audit endpoints — because a background sync job that fails silently is worse than one that doesn't exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  DishLens: from photo to recipe
&lt;/h2&gt;

&lt;p&gt;This is where most of the genuinely hard problems live, because the input is a photo taken by a real person on a real phone — not a clean API payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The image pipeline, before anything else happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File-type and size validation, and pixel-dimension limits, to reject junk before it costs an API call.&lt;/li&gt;
&lt;li&gt;EXIF-safe re-encoding that strips GPS data and normalizes orientation — a privacy requirement as much as a correctness one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laplacian-variance blur detection&lt;/strong&gt; to reject blurry photos before they hit the vision model. This one mattered more than I expected: a blurry photo doesn't just produce a bad recipe, it produces a &lt;em&gt;confidently wrong&lt;/em&gt; one, and rejecting it early is both cheaper and a better user experience than letting the model guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dish detection and generation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Vision handles dish classification, with edge-case handling for things like multi-dish photos (a full table spread instead of one dish) and moderation to filter inappropriate content.&lt;/li&gt;
&lt;li&gt;Recipe generation runs through Anthropic Claude.&lt;/li&gt;
&lt;li&gt;Nutrition data comes from Edamam, matched back to the generated recipe — so the numbers aren't just a model's guess, they're grounded in a real nutrition database.&lt;/li&gt;
&lt;li&gt;Photos themselves are normalized, uploaded, and served from GCS with signed URLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Everything else an API needs to not fall over:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT-based auth shared across services, with per-user rate limiting on the upload endpoint.&lt;/li&gt;
&lt;li&gt;A Redis-backed session store, scoped per user with TTLs.&lt;/li&gt;
&lt;li&gt;Chat that can draw on a user's own synced recipes, not just what Claude generates fresh — and chat sessions that work even when no dish has been scanned, for general recipe/nutrition Q&amp;amp;A.&lt;/li&gt;
&lt;li&gt;The usual account plumbing: password change, account deletion, &lt;code&gt;GET /auth/me&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The React Native app
&lt;/h2&gt;

&lt;p&gt;Built in Expo, developed in structured milestones (M1 through M7, across five "rounds") — brand foundations and auth screens first, then the scan flow, then chat and meal planning, then a full dark mode pass with semantic theme tokens and accessibility work.&lt;/p&gt;

&lt;p&gt;A few things that ate more time than expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Unsupported FormDataPart implementation"&lt;/strong&gt; — a fun way to discover that multipart uploads behave differently across platforms and Expo versions.&lt;/li&gt;
&lt;li&gt;Compressing and resizing photos client-side before upload, both for speed and to keep the backend's size limits happy.&lt;/li&gt;
&lt;li&gt;Proactively refreshing access tokens instead of waiting for a request to fail and refreshing reactively — small change, noticeably better UX.&lt;/li&gt;
&lt;li&gt;Wiring auth state through SecureStore so sessions survive app restarts without storing tokens somewhere they shouldn't be.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The unglamorous part: deployment
&lt;/h2&gt;

&lt;p&gt;If the commit history is honest about anything, it's that deployment ate real hours: Railway build errors, Prisma's engine binaries needing OpenSSL inside Docker, and Turbo v2's strict &lt;code&gt;envMode&lt;/code&gt; silently stripping environment variables it didn't recognize. None of this is interesting to write about, and all of it is the kind of thing that quietly determines whether a project ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Claude Code actually helped
&lt;/h2&gt;

&lt;p&gt;Claude Code was genuinely useful for moving quickly through boilerplate and unfamiliar territory — this is my first time in React Native and Expo. But the bottleneck on a project like this was never typing speed. It was deciding that a blurry photo needs its own rejection path before the vision model ever sees it, that stale vectors need active deletion instead of just letting Pinecone grow, that a background sync job needs an audit endpoint because "it probably worked" isn't good enough. Those are judgment calls that come from domain knowledge and experience, not from a tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;DriveSync and DishLens currently share a monorepo. That's changing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GraceSoft DriveSync&lt;/strong&gt; and &lt;strong&gt;GraceSoft DishLens API&lt;/strong&gt; are moving into separate repos.&lt;/li&gt;
&lt;li&gt;DriveSync is headed toward &lt;strong&gt;open source&lt;/strong&gt;, once I clean it up a bit more.&lt;/li&gt;
&lt;li&gt;DishLens API stays &lt;strong&gt;proprietary&lt;/strong&gt; — it'll power the GraceSoft DishLens app going forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DishLens (the API) is still in bug-fixing mode, and the app is catching up to it feature by feature. More to come as both stabilize.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Still a one-woman show at GraceSoft. If you've built something similar — recipe generation, vector sync pipelines, or your first React Native app — I'd love to hear how you tackled it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>webdev</category>
      <category>buildinpublic</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why GraceSoft Dropped GitHub Copilot for Claude Code</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Mon, 03 Aug 2026 17:54:12 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/why-gracesoft-dropped-github-copilot-for-claude-code-1k40</link>
      <guid>https://dev.to/gracesoftdev/why-gracesoft-dropped-github-copilot-for-claude-code-1k40</guid>
      <description>&lt;p&gt;GitHub Copilot was my default AI coding assistant for a long time. It handled inline completions well and slotted cleanly into my editor workflow.&lt;/p&gt;

&lt;p&gt;In May, I started running Claude Code alongside it — not as a replacement yet, just a parallel track. For the past few months, I've used both at GraceSoft, task by task, watching where each one actually wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  What tipped it
&lt;/h2&gt;

&lt;p&gt;Copilot is strong at fast, local completions — finishing the line or function you're already writing. Where it fell short for me was anything that spanned more than one file: understanding how a change in a service layer should ripple into its tests, its callers, and its docs. That's where Claude Code pulled ahead. It reasons across the codebase rather than just the cursor position, and it can carry out multi-step tasks — refactors, migrations, debugging loops — without me having to babysit every intermediate step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision
&lt;/h2&gt;

&lt;p&gt;Three months of A/B testing on real GraceSoft codebases gave me enough signal: I'm dropping Copilot and moving fully to Claude Code. The difference showed up most in multi-file context handling and agentic task completion — not just autocomplete quality.&lt;/p&gt;

&lt;p&gt;If you're on the fence, don't take my word for it — run your own side-by-side for a sprint or two. Happy to share more specifics on my eval process if there's interest in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>github</category>
    </item>
    <item>
      <title>Migrating from Astro to Laravel + Postgres + Cloudinary — and why my first plan didn't survive contact with reality</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:24:14 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/migrating-from-astro-to-laravel-postgres-cloudinary-and-why-my-first-plan-didnt-survive-goc</link>
      <guid>https://dev.to/gracesoftdev/migrating-from-astro-to-laravel-postgres-cloudinary-and-why-my-first-plan-didnt-survive-goc</guid>
      <description>&lt;p&gt;Quick devlog on a content migration I'm running for my personal site (davdevs-laravel): moving from a flat-file Astro content repo into a Laravel app backed by PostgreSQL, with Cloudinary handling media.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wasted day (sort of)
&lt;/h2&gt;

&lt;p&gt;I spent a full day writing .md guideline documents — step-by-step instructions for Claude Code to handle the migration autonomously.&lt;/p&gt;

&lt;p&gt;The next day, reviewing it with fresh eyes, I realized the whole plan was built around the wrong topology: I'd designed it as a local-to-local transfer, since both the old and new repos live on my dev machine right now. But the actual migration target is local-to-cloud (Cloudinary for media, hosted Postgres for data), and that changes sequencing, validation, and rollback enough that the original docs weren't salvageable as-is. Today's commits tell that story pretty directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated migration docs&lt;/li&gt;
&lt;li&gt;Updated migration plan to audit images&lt;/li&gt;
&lt;li&gt;feat: content migration audit + import commands (Phase 22, step 1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm also still somewhat skeptical of how cleanly Claude Code will execute a migration like this end-to-end — subtle content breakage is easy to miss — so I made the image importer resumable and resilient to per-file failures (Make migration:import-images resumable and resilient to per-file failures) rather than treating it as one big atomic job. If it dies on file 340 of 500, I don't want to start over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two content types that needed real design decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Tool pages with attached React components
&lt;/h3&gt;

&lt;p&gt;Each /tools post has a React component embedded alongside it. The instruction to Claude Code was scoped narrowly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migrate only the components inside /tools&lt;/li&gt;
&lt;li&gt;Update the color scheme to match the new site — e.g., yellow buttons replacing blue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shipped as Add React Component Manager for Tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ebook product pages (uniquely designed, per ebook)
&lt;/h3&gt;

&lt;p&gt;This was the harder one. Every ebook product page was hand-designed as a one-off — great for bespoke launches, terrible for migrating raw code into storable DB records.&lt;/p&gt;

&lt;p&gt;The solution: stop treating each page as unique. Consolidate all ebook product pages into two layout variants — individual and bundle — and adapt the publications module to support full design customization within those two variants. This landed as Publication Template Manager for eBooks, in the same commit as the tools work — 87346c7: Add React Component Manager for Tools + Publication Template Manager for eBooks.&lt;/p&gt;

&lt;p&gt;Worth noting this builds on a longer architecture already in place from prior phases — publication manager, taxonomy CRUD, headless REST API, JSON-LD/OG tags — so the template consolidation slots into an existing content model rather than a bolt-on.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Starting Development on GraceSoft RSVP — A Real-Time Wedding Operations Dashboard Powered by Google Sheets</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Fri, 15 May 2026 06:23:40 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/starting-development-on-gracesoft-rsvp-a-real-time-wedding-operations-dashboard-powered-by-google-642</link>
      <guid>https://dev.to/gracesoftdev/starting-development-on-gracesoft-rsvp-a-real-time-wedding-operations-dashboard-powered-by-google-642</guid>
      <description>&lt;p&gt;We’ve officially started development on GraceSoft RSVP — a mobile-first, real-time wedding operations dashboard built around Google Sheets synchronization.&lt;/p&gt;

&lt;p&gt;The initial idea came from a simple real-world question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is it possible to build a live dashboard connected to a Google Spreadsheet that updates automatically as the spreadsheet changes?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was yes — but rather than building a simple spreadsheet viewer, we decided to approach the problem like an operational platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision
&lt;/h2&gt;

&lt;p&gt;GraceSoft RSVP is designed to help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;couples&lt;/li&gt;
&lt;li&gt;coordinators&lt;/li&gt;
&lt;li&gt;wedding helpers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…manage guest operations in real time.&lt;/p&gt;

&lt;p&gt;The goal is to transform familiar spreadsheet workflows into a responsive, mobile-friendly operational dashboard experience.&lt;/p&gt;

&lt;p&gt;Instead of requiring users to learn a custom admin panel, the spreadsheet itself becomes the content management system.&lt;/p&gt;

&lt;p&gt;Users continue updating guest information in Google Sheets while the dashboard automatically reflects changes live.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Google Sheets?
&lt;/h1&gt;

&lt;p&gt;One of the most interesting engineering aspects of this project is that the source dataset is intentionally flexible.&lt;/p&gt;

&lt;p&gt;We may not always control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;column naming&lt;/li&gt;
&lt;li&gt;column order&lt;/li&gt;
&lt;li&gt;optional fields&lt;/li&gt;
&lt;li&gt;spreadsheet formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means the application cannot rely on hardcoded indexes or rigid schemas.&lt;/p&gt;

&lt;p&gt;The dashboard has to adapt dynamically.&lt;/p&gt;

&lt;p&gt;That led us toward building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dynamic header mapping&lt;/li&gt;
&lt;li&gt;normalization layers&lt;/li&gt;
&lt;li&gt;typed transformation pipelines&lt;/li&gt;
&lt;li&gt;resilient data parsing systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;rather than directly binding UI components to spreadsheet cells.&lt;/p&gt;




&lt;h1&gt;
  
  
  Core Architecture
&lt;/h1&gt;

&lt;p&gt;The current planned architecture looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google Sheets
      ↓
Google Sheets API
      ↓
Normalization Layer
      ↓
Typed Guest Models
      ↓
Dashboard API
      ↓
Reactive Mobile UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than exposing the spreadsheet directly to the frontend, the server transforms raw spreadsheet data into structured internal models.&lt;/p&gt;

&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;schema flexibility&lt;/li&gt;
&lt;li&gt;safer parsing&lt;/li&gt;
&lt;li&gt;cleaner frontend architecture&lt;/li&gt;
&lt;li&gt;reusable analytics systems&lt;/li&gt;
&lt;li&gt;future scalability&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Technical Goals
&lt;/h1&gt;

&lt;p&gt;This project is also an opportunity to explore several frontend engineering concepts in a practical production-like environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planned Technical Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dynamic Header Mapping
&lt;/h3&gt;

&lt;p&gt;The dashboard should continue working even if spreadsheet columns change names or move positions.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RSVP
RSVP Status
rsvp_status
Guest RSVP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these should map correctly.&lt;/p&gt;

&lt;p&gt;This introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;normalization utilities&lt;/li&gt;
&lt;li&gt;synonym matching&lt;/li&gt;
&lt;li&gt;defensive parsing strategies&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Real-Time Updates
&lt;/h3&gt;

&lt;p&gt;The dashboard should update automatically without refreshing the page.&lt;/p&gt;

&lt;p&gt;The initial implementation will likely use polling with SWR refresh intervals.&lt;/p&gt;

&lt;p&gt;Later phases may introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-Sent Events (SSE)&lt;/li&gt;
&lt;li&gt;streaming updates&lt;/li&gt;
&lt;li&gt;activity feeds&lt;/li&gt;
&lt;li&gt;reactive state synchronization&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Typed Data Models
&lt;/h3&gt;

&lt;p&gt;All spreadsheet data will eventually be transformed into typed models.&lt;/p&gt;

&lt;p&gt;Example:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Guest&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&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="nl"&gt;side&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="nl"&gt;pax&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="nl"&gt;rsvpStatus&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="nl"&gt;arrivalStatus&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="nl"&gt;diet&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="nl"&gt;table&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a clean separation between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;external datasets&lt;/li&gt;
&lt;li&gt;application logic&lt;/li&gt;
&lt;li&gt;presentation layers&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Mobile-First Operations UI
&lt;/h3&gt;

&lt;p&gt;Wedding coordinators and helpers are likely to use the dashboard primarily on phones during live events.&lt;/p&gt;

&lt;p&gt;The UI is therefore being designed mobile-first with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;touch-friendly cards&lt;/li&gt;
&lt;li&gt;responsive layouts&lt;/li&gt;
&lt;li&gt;searchable guest lists&lt;/li&gt;
&lt;li&gt;real-time status indicators&lt;/li&gt;
&lt;li&gt;operational analytics&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Planned Dashboard Views
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Operations Dashboard
&lt;/h2&gt;

&lt;p&gt;A high-level overview screen showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;total guests&lt;/li&gt;
&lt;li&gt;confirmed RSVPs&lt;/li&gt;
&lt;li&gt;arrivals&lt;/li&gt;
&lt;li&gt;dietary analytics&lt;/li&gt;
&lt;li&gt;live activity updates&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Arrival View
&lt;/h2&gt;

&lt;p&gt;A view-only operational guest list showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;guests who have arrived&lt;/li&gt;
&lt;li&gt;side allocation&lt;/li&gt;
&lt;li&gt;dietary restrictions&lt;/li&gt;
&lt;li&gt;table information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This screen is intended for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;coordinators&lt;/li&gt;
&lt;li&gt;helpers&lt;/li&gt;
&lt;li&gt;ushers&lt;/li&gt;
&lt;li&gt;logistics support&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Nice-to-Have Features
&lt;/h1&gt;

&lt;p&gt;Some planned experimental features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;QR-based guest check-in&lt;/li&gt;
&lt;li&gt;passcode-protected dashboards&lt;/li&gt;
&lt;li&gt;activity logs&lt;/li&gt;
&lt;li&gt;live synchronization streams&lt;/li&gt;
&lt;li&gt;operational analytics&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Current Tech Stack
&lt;/h1&gt;

&lt;p&gt;Current planned stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;li&gt;shadcn/ui&lt;/li&gt;
&lt;li&gt;Recharts&lt;/li&gt;
&lt;li&gt;Google Sheets API&lt;/li&gt;
&lt;li&gt;SWR&lt;/li&gt;
&lt;li&gt;Zod&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hosting will likely be handled through Vercel.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security &amp;amp; API Design
&lt;/h1&gt;

&lt;p&gt;One important engineering decision is that the raw spreadsheet will never be exposed publicly.&lt;/p&gt;

&lt;p&gt;The application uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server-side Google Sheets fetching&lt;/li&gt;
&lt;li&gt;readonly service account permissions&lt;/li&gt;
&lt;li&gt;internal normalization pipelines&lt;/li&gt;
&lt;li&gt;environment-based credential management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The spreadsheet behaves like a backend CMS, while the frontend remains a structured operational interface.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Project Is Interesting
&lt;/h1&gt;

&lt;p&gt;What started as a wedding helper tool quickly evolved into something broader:&lt;/p&gt;

&lt;p&gt;a lightweight real-time event operations platform.&lt;/p&gt;

&lt;p&gt;The underlying architecture could eventually support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conferences&lt;/li&gt;
&lt;li&gt;ministry events&lt;/li&gt;
&lt;li&gt;church attendance systems&lt;/li&gt;
&lt;li&gt;volunteer operations&lt;/li&gt;
&lt;li&gt;registration workflows&lt;/li&gt;
&lt;li&gt;hospitality coordination&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Next Steps
&lt;/h1&gt;

&lt;p&gt;Current milestones include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setting up Google Sheets synchronization&lt;/li&gt;
&lt;li&gt;building normalization utilities&lt;/li&gt;
&lt;li&gt;implementing typed guest models&lt;/li&gt;
&lt;li&gt;creating responsive dashboard layouts&lt;/li&gt;
&lt;li&gt;implementing real-time polling updates&lt;/li&gt;
&lt;li&gt;designing reusable analytics components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll continue documenting the development process publicly as the project evolves.&lt;/p&gt;

&lt;p&gt;More updates soon 👀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I Built a WhatsApp Booking Agent — The Hard Part Wasn’t AI</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Sat, 02 May 2026 09:45:05 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/i-built-a-whatsapp-booking-agent-the-hard-part-wasnt-ai-1d4n</link>
      <guid>https://dev.to/gracesoftdev/i-built-a-whatsapp-booking-agent-the-hard-part-wasnt-ai-1d4n</guid>
      <description>&lt;h1&gt;
  
  
  Building GraceSoft Sentinel Concierge: From WhatsApp Bot to Production Booking Agent
&lt;/h1&gt;

&lt;p&gt;I spent the last stretch building &lt;strong&gt;GraceSoft Sentinel Concierge&lt;/strong&gt; — a WhatsApp-first concierge agent designed to handle customer enquiries, FAQs, bookings, and business handover without requiring users to download another app.&lt;/p&gt;

&lt;p&gt;What started as “a simple WhatsApp assistant” quickly turned into a full production exercise in systems design, operational resilience, and the kind of edge-case engineering that only shows up when real users start sending messy messages.&lt;/p&gt;

&lt;p&gt;This is the dev log of what I built, what broke, and what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  What GraceSoft Sentinel Concierge Is
&lt;/h2&gt;

&lt;p&gt;GraceSoft Sentinel Concierge is a &lt;strong&gt;WhatsApp concierge agent&lt;/strong&gt; for service businesses.&lt;/p&gt;

&lt;p&gt;Its job is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;answer FAQs&lt;/li&gt;
&lt;li&gt;detect booking intent&lt;/li&gt;
&lt;li&gt;suggest available slots&lt;/li&gt;
&lt;li&gt;create bookings&lt;/li&gt;
&lt;li&gt;escalate to a human when confidence drops&lt;/li&gt;
&lt;li&gt;avoid sounding robotic while doing all of the above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood, it’s doing much more than that.&lt;/p&gt;

&lt;p&gt;This became less of a chatbot project and more of a &lt;strong&gt;stateful booking orchestration system&lt;/strong&gt; with WhatsApp as the UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;I kept the stack intentionally lean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js + Express&lt;/strong&gt; — webhook and API layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; — typed flows and safer service boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt; — schema and database access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; — persistent booking and business data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — ephemeral booking state, session memory, dedupe, rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Calendar API&lt;/strong&gt; — slot availability + booking sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — local + deployment consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Railway&lt;/strong&gt; — deployment and runtime hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stack gave me the flexibility to move fast without overengineering too early.&lt;/p&gt;




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

&lt;p&gt;Over a few intense iterations, Sentinel Concierge grew from a webhook listener into a proper booking system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Foundation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;WhatsApp webhook handling&lt;/li&gt;
&lt;li&gt;environment validation&lt;/li&gt;
&lt;li&gt;Prisma + MySQL setup&lt;/li&gt;
&lt;li&gt;Redis-backed session state&lt;/li&gt;
&lt;li&gt;basic FAQ routing&lt;/li&gt;
&lt;li&gt;structured logging with PII redaction&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 2: Intent + Handover
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;FAQ intent matching&lt;/li&gt;
&lt;li&gt;booking intent detection&lt;/li&gt;
&lt;li&gt;fallback confidence handling&lt;/li&gt;
&lt;li&gt;business handover escalation&lt;/li&gt;
&lt;li&gt;manual help command routing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 3: Booking Engine
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;date/time extraction from natural messages&lt;/li&gt;
&lt;li&gt;booking slot generation&lt;/li&gt;
&lt;li&gt;preferred-time ordering&lt;/li&gt;
&lt;li&gt;invalid date rejection&lt;/li&gt;
&lt;li&gt;timezone-safe slot formatting&lt;/li&gt;
&lt;li&gt;Google Calendar booking sync&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 4: Ops Hardening
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;webhook dedupe&lt;/li&gt;
&lt;li&gt;Redis reconnect resilience&lt;/li&gt;
&lt;li&gt;startup health probes&lt;/li&gt;
&lt;li&gt;deployment readiness checks&lt;/li&gt;
&lt;li&gt;traceable logs&lt;/li&gt;
&lt;li&gt;prompt injection safety&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 5: Business Logic Hardening
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;public holiday awareness&lt;/li&gt;
&lt;li&gt;“day in lieu” logic&lt;/li&gt;
&lt;li&gt;weekday/flexi-hours mapping&lt;/li&gt;
&lt;li&gt;legal pages&lt;/li&gt;
&lt;li&gt;booking cancellation TODOs&lt;/li&gt;
&lt;li&gt;human-readable booking references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, it stopped being “just a bot.”&lt;/p&gt;

&lt;p&gt;It became a production system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Parts (and What Broke)
&lt;/h2&gt;

&lt;p&gt;This was the real work.&lt;/p&gt;

&lt;p&gt;The code was rarely the hardest part.&lt;/p&gt;

&lt;p&gt;The hard part was making the system behave correctly under real-world ambiguity. That’s where most of the engineering time went — which is also where most “AI agent” demos quietly fall apart. (&lt;a href="https://dev.to/tyu1996/software-engineering-in-2026-a-view-from-the-server-room-3kag?utm_source=chatgpt.com"&gt;DEV Community&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  1. WhatsApp Is Not a Clean Interface
&lt;/h3&gt;

&lt;p&gt;Users do not send clean structured input.&lt;/p&gt;

&lt;p&gt;They send:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“tmr 3ish can?”&lt;/li&gt;
&lt;li&gt;“this sat after lunch”&lt;/li&gt;
&lt;li&gt;“book for next week maybe”&lt;/li&gt;
&lt;li&gt;“actually nvm can cancel”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Natural language booking sounds easy until you have to convert vague intent into deterministic business actions.&lt;/p&gt;

&lt;p&gt;The challenge wasn’t understanding language.&lt;/p&gt;

&lt;p&gt;The challenge was deciding &lt;strong&gt;when not to act&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That meant building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ambiguity thresholds&lt;/li&gt;
&lt;li&gt;safe fallbacks&lt;/li&gt;
&lt;li&gt;clarification loops&lt;/li&gt;
&lt;li&gt;escalation rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hardest part of conversational systems is rarely parsing text.&lt;/p&gt;

&lt;p&gt;It’s preventing bad automation.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Booking Logic Is Mostly Edge Cases
&lt;/h3&gt;

&lt;p&gt;Booking systems sound simple until you hit reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timezone drift&lt;/li&gt;
&lt;li&gt;invalid dates&lt;/li&gt;
&lt;li&gt;public holidays&lt;/li&gt;
&lt;li&gt;day-in-lieu holidays&lt;/li&gt;
&lt;li&gt;flexi business hours&lt;/li&gt;
&lt;li&gt;conflicting calendar events&lt;/li&gt;
&lt;li&gt;duplicate booking attempts&lt;/li&gt;
&lt;li&gt;partial confirmations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the complexity in Sentinel Concierge came from one thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;making sure the system never confidently books the wrong thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This meant spending more time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slot filtering&lt;/li&gt;
&lt;li&gt;booking validation&lt;/li&gt;
&lt;li&gt;idempotency&lt;/li&gt;
&lt;li&gt;human-readable confirmation states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…than on the chatbot itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Webhooks Are Messy in Production
&lt;/h3&gt;

&lt;p&gt;Webhook systems are noisy.&lt;/p&gt;

&lt;p&gt;Retries happen.&lt;br&gt;
Duplicate events happen.&lt;br&gt;
Out-of-order events happen.&lt;/p&gt;

&lt;p&gt;A clean local demo does not prepare you for production webhook behaviour.&lt;/p&gt;

&lt;p&gt;I had to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dedupe protection&lt;/li&gt;
&lt;li&gt;replay-safe handling&lt;/li&gt;
&lt;li&gt;startup resilience&lt;/li&gt;
&lt;li&gt;Redis reconnect recovery&lt;/li&gt;
&lt;li&gt;safer initialization for Prisma&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was one of the biggest shifts in the project:&lt;/p&gt;

&lt;p&gt;moving from “it works locally” to “it survives production.”&lt;/p&gt;




&lt;h3&gt;
  
  
  4. The Real Product Was State
&lt;/h3&gt;

&lt;p&gt;The biggest architectural shift was realising the product was not the WhatsApp layer.&lt;/p&gt;

&lt;p&gt;The product was &lt;strong&gt;state management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;WhatsApp is just the interface.&lt;/p&gt;

&lt;p&gt;The real system is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the user already said&lt;/li&gt;
&lt;li&gt;what step they’re in&lt;/li&gt;
&lt;li&gt;whether they’re asking or confirming&lt;/li&gt;
&lt;li&gt;whether the system is waiting for clarification&lt;/li&gt;
&lt;li&gt;whether escalation already happened&lt;/li&gt;
&lt;li&gt;whether the booking should still be considered active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That changed how I designed everything.&lt;/p&gt;

&lt;p&gt;Redis stopped being “nice to have” caching.&lt;/p&gt;

&lt;p&gt;It became the operational backbone of the conversation flow.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. “AI” Was the Least Interesting Part
&lt;/h3&gt;

&lt;p&gt;The most useful lesson from building this:&lt;/p&gt;

&lt;p&gt;the intelligence was not in the model.&lt;/p&gt;

&lt;p&gt;It was in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;routing&lt;/li&gt;
&lt;li&gt;safeguards&lt;/li&gt;
&lt;li&gt;confidence thresholds&lt;/li&gt;
&lt;li&gt;fallback rules&lt;/li&gt;
&lt;li&gt;operational boundaries&lt;/li&gt;
&lt;li&gt;deterministic business logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM helped with interpretation.&lt;/p&gt;

&lt;p&gt;The system did the real work.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A lot of “AI product” demos are really just prompt wrappers.&lt;/p&gt;

&lt;p&gt;This project became useful only when the operational logic became stronger than the prompt.&lt;/p&gt;




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

&lt;p&gt;A few things became very clear while building this.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The happy path is a lie
&lt;/h3&gt;

&lt;p&gt;The happy path is the demo.&lt;/p&gt;

&lt;p&gt;The product is everything outside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Operational resilience matters more than clever prompts
&lt;/h3&gt;

&lt;p&gt;Prompt quality matters.&lt;/p&gt;

&lt;p&gt;But retries, dedupe, health checks, and state recovery matter more.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Most AI systems fail at boundaries
&lt;/h3&gt;

&lt;p&gt;Not generation.&lt;/p&gt;

&lt;p&gt;Boundaries.&lt;/p&gt;

&lt;p&gt;When to ask.&lt;br&gt;
When to wait.&lt;br&gt;
When to escalate.&lt;br&gt;
When to stop.&lt;/p&gt;

&lt;p&gt;That’s the real product.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Booking systems are trust systems
&lt;/h3&gt;

&lt;p&gt;Users forgive slow replies.&lt;/p&gt;

&lt;p&gt;They do not forgive wrong bookings.&lt;/p&gt;

&lt;p&gt;Reliability matters more than speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Next up for Sentinel Concierge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;booking cancellation flow&lt;/li&gt;
&lt;li&gt;rescheduling flow&lt;/li&gt;
&lt;li&gt;admin controls&lt;/li&gt;
&lt;li&gt;booking audit history&lt;/li&gt;
&lt;li&gt;business-facing dashboard&lt;/li&gt;
&lt;li&gt;analytics on drop-off / escalation / booking completion&lt;/li&gt;
&lt;li&gt;multi-business tenancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The WhatsApp layer works.&lt;/p&gt;

&lt;p&gt;Now the real product work begins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;GraceSoft Sentinel Concierge started as a WhatsApp AI assistant.&lt;/p&gt;

&lt;p&gt;It turned into a lesson in production systems, conversational state, and the uncomfortable truth that most of the work in “AI products” has very little to do with AI.&lt;/p&gt;

&lt;p&gt;And honestly?&lt;/p&gt;

&lt;p&gt;That was the most useful part of building it.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>node</category>
      <category>typescript</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Day 9: File Uploads Are More Dangerous Than I Thought</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Sat, 25 Apr 2026 06:04:51 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-9-file-uploads-are-more-dangerous-than-i-thought-iaf</link>
      <guid>https://dev.to/gracesoftdev/day-9-file-uploads-are-more-dangerous-than-i-thought-iaf</guid>
      <description>&lt;p&gt;I already had an attachments system.&lt;/p&gt;

&lt;p&gt;It worked across all modules.&lt;/p&gt;

&lt;p&gt;Reusable. Clean. Flexible.&lt;/p&gt;




&lt;p&gt;But I hadn’t thought deeply about one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Privacy.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚠️ The Risk
&lt;/h2&gt;

&lt;p&gt;Files can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal data&lt;/li&gt;
&lt;li&gt;Sensitive documents&lt;/li&gt;
&lt;li&gt;Things that should never be public&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;And if handled badly?&lt;/p&gt;

&lt;p&gt;They become a liability.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 What I Changed
&lt;/h2&gt;

&lt;p&gt;I redesigned attachments with privacy in mind:&lt;/p&gt;




&lt;h3&gt;
  
  
  Private by Default
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Files are private unless explicitly made public&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Signed URLs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Temporary access links&lt;/li&gt;
&lt;li&gt;Expire after a set time&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Transparency
&lt;/h3&gt;

&lt;p&gt;Each file shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who uploaded it&lt;/li&gt;
&lt;li&gt;Where it’s used&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 The Insight
&lt;/h2&gt;

&lt;p&gt;Files are not just assets.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;They are data.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And they need the same level of care.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Now my attachments system isn’t just reusable.&lt;/p&gt;

&lt;p&gt;It’s:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Safe by design.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Which means every future module benefits automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Final Thought
&lt;/h2&gt;

&lt;p&gt;At this point, something clicked.&lt;/p&gt;




&lt;p&gt;I’m no longer just building features.&lt;/p&gt;

&lt;p&gt;I’m building:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;defaults that shape behaviour&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Day 8: I Built a Timeline to See the Life of Data</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Wed, 22 Apr 2026 04:49:08 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-8-i-built-a-timeline-to-see-the-life-of-data-2n7i</link>
      <guid>https://dev.to/gracesoftdev/day-8-i-built-a-timeline-to-see-the-life-of-data-2n7i</guid>
      <description>&lt;p&gt;Once I added context…&lt;/p&gt;

&lt;p&gt;I still felt something was missing.&lt;/p&gt;




&lt;p&gt;Data wasn’t static.&lt;/p&gt;

&lt;p&gt;It changed over time.&lt;/p&gt;




&lt;p&gt;So I asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if I could see the &lt;em&gt;history&lt;/em&gt; of a piece of data?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧵 The Idea
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Data Timeline&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 What It Shows
&lt;/h2&gt;

&lt;p&gt;For each user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When data was created&lt;/li&gt;
&lt;li&gt;When it was updated&lt;/li&gt;
&lt;li&gt;Whether it came from Stripe / Postmark&lt;/li&gt;
&lt;li&gt;Who triggered it (admin/system)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤯 Why This Is Powerful
&lt;/h2&gt;

&lt;p&gt;Instead of guessing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What happened here?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can now see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s exactly what happened.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔍 Debugging Becomes Easier
&lt;/h2&gt;

&lt;p&gt;This also changed how I debug things.&lt;/p&gt;




&lt;p&gt;Instead of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;digging through logs&lt;/li&gt;
&lt;li&gt;checking multiple systems&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I can just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Look at the timeline.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 Unexpected Benefit
&lt;/h2&gt;

&lt;p&gt;This isn’t just a dev feature.&lt;/p&gt;

&lt;p&gt;It’s also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audit-friendly&lt;/li&gt;
&lt;li&gt;Compliance-friendly&lt;/li&gt;
&lt;li&gt;Support-friendly&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Day 7: Showing Data Without Context Is Dangerous</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Tue, 21 Apr 2026 07:34:59 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-7-showing-data-without-context-is-dangerous-gfd</link>
      <guid>https://dev.to/gracesoftdev/day-7-showing-data-without-context-is-dangerous-gfd</guid>
      <description>&lt;p&gt;After masking data, I ran into another problem.&lt;/p&gt;




&lt;p&gt;Even when data is safe…&lt;/p&gt;

&lt;p&gt;It can still be confusing.&lt;/p&gt;




&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;You see a user email.&lt;/p&gt;

&lt;p&gt;But you don’t know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where it came from&lt;/li&gt;
&lt;li&gt;Why it exists&lt;/li&gt;
&lt;li&gt;Whether it’s still needed&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Raw data ≠ meaningful data&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 The Fix
&lt;/h2&gt;

&lt;p&gt;I redesigned the user view to include:&lt;/p&gt;




&lt;h3&gt;
  
  
  👤 Data Transparency
&lt;/h3&gt;

&lt;p&gt;Each user now shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What data is stored&lt;/li&gt;
&lt;li&gt;Why it’s stored&lt;/li&gt;
&lt;li&gt;Where it came from&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;(form, webhook, import, etc.)&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Why This Matters
&lt;/h2&gt;

&lt;p&gt;Because transparency builds trust.&lt;/p&gt;




&lt;p&gt;Not just for users.&lt;/p&gt;

&lt;p&gt;But for developers too.&lt;/p&gt;




&lt;p&gt;You don’t want to guess:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why is this field here?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;You want to know instantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚧 Implementation Notes
&lt;/h2&gt;

&lt;p&gt;This reused a lot of what I already built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhook metadata&lt;/li&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;System tracking&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Which made me realise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Good architecture makes good UI easier.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Day 6: The One Toggle That Changed My Entire System</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Mon, 20 Apr 2026 05:04:32 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-6-the-one-toggle-that-changed-my-entire-system-17ob</link>
      <guid>https://dev.to/gracesoftdev/day-6-the-one-toggle-that-changed-my-entire-system-17ob</guid>
      <description>&lt;p&gt;I wanted something simple.&lt;/p&gt;

&lt;p&gt;Not a policy.&lt;br&gt;&lt;br&gt;
Not a config file.&lt;br&gt;&lt;br&gt;
Not a checklist.&lt;/p&gt;




&lt;p&gt;Something visible.&lt;/p&gt;

&lt;p&gt;Something immediate.&lt;/p&gt;




&lt;p&gt;That’s how I landed on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Privacy Mode&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎛️ What It Does
&lt;/h2&gt;

&lt;p&gt;When Privacy Mode is ON:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emails are masked → &lt;code&gt;d***@gmail.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Names are masked → &lt;code&gt;D*** L***&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Attachments are blurred or hidden&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔥 The Important Part
&lt;/h2&gt;

&lt;p&gt;It’s not just a feature.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s ON by default in production.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚠️ Why Default Matters
&lt;/h2&gt;

&lt;p&gt;Because defaults define behaviour.&lt;/p&gt;




&lt;p&gt;If Privacy Mode was OFF by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Devs forget to turn it on&lt;/li&gt;
&lt;li&gt;Data gets exposed&lt;/li&gt;
&lt;li&gt;Problems happen&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;So instead, I made the safe path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The easiest path.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 Design Insight
&lt;/h2&gt;

&lt;p&gt;Good systems don’t rely on memory.&lt;/p&gt;

&lt;p&gt;They rely on &lt;strong&gt;defaults&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚧 Implementation Thought
&lt;/h2&gt;

&lt;p&gt;This wasn’t hard technically.&lt;/p&gt;

&lt;p&gt;But it required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI masking logic&lt;/li&gt;
&lt;li&gt;Conditional rendering&lt;/li&gt;
&lt;li&gt;Environment awareness&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Simple idea.&lt;br&gt;&lt;br&gt;
Big impact.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Day 5: I Almost Over-Engineered Everything</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Fri, 17 Apr 2026 08:47:41 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-5-i-almost-over-engineered-everything-1eo5</link>
      <guid>https://dev.to/gracesoftdev/day-5-i-almost-over-engineered-everything-1eo5</guid>
      <description>&lt;p&gt;At this point, GraceSoft Core was starting to take shape.&lt;/p&gt;

&lt;p&gt;Architecture. Patterns. Principles.&lt;/p&gt;

&lt;p&gt;Everything felt… solid.&lt;/p&gt;




&lt;p&gt;And then I caught myself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I was about to over-engineer the whole thing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ⚖️ The Tension
&lt;/h2&gt;

&lt;p&gt;There’s always this trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build it &lt;em&gt;right&lt;/em&gt;
vs&lt;/li&gt;
&lt;li&gt;Build it &lt;em&gt;fast&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;And I’ve been on both extremes before:&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Too Fast
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hack things together&lt;/li&gt;
&lt;li&gt;Ship quickly&lt;/li&gt;
&lt;li&gt;Pay the price later&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🧱 Too Perfect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Design everything upfront&lt;/li&gt;
&lt;li&gt;Endless planning&lt;/li&gt;
&lt;li&gt;Nothing actually ships&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🤯 The Realisation
&lt;/h2&gt;

&lt;p&gt;GraceSoft Core was at risk of becoming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A system that’s beautifully designed… but never used.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;I asked myself a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What’s the smallest version of this that actually works?”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perfect architecture&lt;/li&gt;
&lt;li&gt;Full feature set&lt;/li&gt;
&lt;li&gt;Every edge case covered&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Something real I can use in my current app.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🔑 New Principle
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Build just enough core to remove friction — not all friction.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚧 What I’m Focusing On Now
&lt;/h2&gt;

&lt;p&gt;Instead of everything, I’m narrowing down to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth (done properly once)&lt;/li&gt;
&lt;li&gt;Basic integrations (Stripe, email)&lt;/li&gt;
&lt;li&gt;Clean project structure&lt;/li&gt;
&lt;li&gt;Reusable UI foundations&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Everything else?&lt;/p&gt;

&lt;p&gt;Can come later.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Honest Truth
&lt;/h2&gt;

&lt;p&gt;I still feel the urge to overbuild.&lt;/p&gt;

&lt;p&gt;To make it “complete”.&lt;/p&gt;




&lt;p&gt;But I’m learning this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A system becomes real when it’s used — not when it’s finished.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Day 4: Why I’m Designing Security First (Not Features)</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Tue, 14 Apr 2026 01:57:25 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-4-why-im-designing-security-first-not-features-oo0</link>
      <guid>https://dev.to/gracesoftdev/day-4-why-im-designing-security-first-not-features-oo0</guid>
      <description>&lt;p&gt;Most projects start with features.&lt;/p&gt;

&lt;p&gt;I’m starting with something else:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚨 Why?
&lt;/h2&gt;

&lt;p&gt;Because retrofitting security is painful.&lt;/p&gt;

&lt;p&gt;And I’ve felt that pain already.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 What “Security-First” Means Here
&lt;/h2&gt;

&lt;p&gt;For GraceSoft Core, it means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear separation of internal vs public data (ID vs UUID)&lt;/li&gt;
&lt;li&gt;Controlled integrations (Stripe, webhooks)&lt;/li&gt;
&lt;li&gt;Environment-aware configurations&lt;/li&gt;
&lt;li&gt;Safe defaults everywhere&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚖️ Trade-Off
&lt;/h2&gt;

&lt;p&gt;Security-first doesn’t mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Over-engineering&lt;/li&gt;
&lt;li&gt;Slowing everything down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Making the &lt;em&gt;safe way&lt;/em&gt; the &lt;em&gt;default way&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 Design Principle
&lt;/h2&gt;

&lt;p&gt;If a developer has to &lt;em&gt;remember&lt;/em&gt; to be secure…&lt;/p&gt;

&lt;p&gt;The system is already flawed.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚧 Still in Progress
&lt;/h2&gt;

&lt;p&gt;I’m still figuring things out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vector DB support&lt;/li&gt;
&lt;li&gt;API structure boundaries&lt;/li&gt;
&lt;li&gt;Module separation&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;But one thing is clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security is not a feature. It’s the foundation.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Day 3: I’m Designing a System Without Writing Code (Yet)</title>
      <dc:creator>GraceSoft</dc:creator>
      <pubDate>Mon, 13 Apr 2026 03:05:05 +0000</pubDate>
      <link>https://dev.to/gracesoftdev/day-3-im-designing-a-system-without-writing-code-yet-b45</link>
      <guid>https://dev.to/gracesoftdev/day-3-im-designing-a-system-without-writing-code-yet-b45</guid>
      <description>&lt;p&gt;This might sound strange:&lt;/p&gt;

&lt;p&gt;I’m building a system…&lt;/p&gt;

&lt;p&gt;Without writing any code.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Why?
&lt;/h2&gt;

&lt;p&gt;Because I’ve made this mistake before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jumping into coding too early&lt;/li&gt;
&lt;li&gt;Locking in bad decisions&lt;/li&gt;
&lt;li&gt;Refactoring everything later&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;So this time, I’m doing something different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’m designing the system first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧱 What I’m Defining
&lt;/h2&gt;

&lt;p&gt;Right now, I’m working from a requirements doc that outlines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tech stack decisions&lt;/li&gt;
&lt;li&gt;Architecture rules&lt;/li&gt;
&lt;li&gt;Integration points&lt;/li&gt;
&lt;li&gt;System boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Yes, I’m treating this like a real product 😅)&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Some Core Decisions
&lt;/h2&gt;

&lt;p&gt;Here’s what’s already shaping up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailwind + Vite as default stack&lt;/li&gt;
&lt;li&gt;Tokenised fonts and brand system&lt;/li&gt;
&lt;li&gt;Structured asset handling (logos, wordmarks, env toggles)&lt;/li&gt;
&lt;li&gt;Stripe + webhook-first integrations&lt;/li&gt;
&lt;li&gt;REST + optional GraphQL&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Everything is being designed to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusable&lt;/li&gt;
&lt;li&gt;Predictable&lt;/li&gt;
&lt;li&gt;Secure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔗 Bigger Vision
&lt;/h2&gt;

&lt;p&gt;This isn’t just a boilerplate.&lt;/p&gt;

&lt;p&gt;It’s meant to connect to a bigger system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GraceSoft HQ&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Where apps can plug into shared services like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Feedback&lt;/li&gt;
&lt;li&gt;Product data&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧠 vs ✋
&lt;/h2&gt;

&lt;p&gt;Here’s the reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My brain is designing systems&lt;/li&gt;
&lt;li&gt;My hands are still fixing bugs in my current app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And somehow…&lt;/p&gt;

&lt;p&gt;Both are moving forward at the same time.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>laravel</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
