<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[My Journey building a Suite of Trade based Estimate Apps]]></title><description><![CDATA[My Journey building a Suite of Trade based Estimate Apps]]></description><link>https://knoxsapps.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ab62fc23d8658bcbd75a8ea/c2aeb042-f252-4dab-8381-bddd82090e79.jpg</url><title>My Journey building a Suite of Trade based Estimate Apps</title><link>https://knoxsapps.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 23:57:59 GMT</lastBuildDate><atom:link href="https://knoxsapps.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building Offline-First Field Quoting on Capacitor + Dexie (A Practical Architecture)]]></title><description><![CDATA[Architecture notes for local-first mobile quoting apps: Dexie persistence, photo capture, on-device PDF generation, and reminder scheduling.]]></description><link>https://knoxsapps.hashnode.dev/building-offline-first-field-quoting-on-capacitor-dexie-a-practical-architecture</link><guid isPermaLink="true">https://knoxsapps.hashnode.dev/building-offline-first-field-quoting-on-capacitor-dexie-a-practical-architecture</guid><category><![CDATA[capacitor]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[indexeddb]]></category><category><![CDATA[React]]></category><category><![CDATA[mobile]]></category><category><![CDATA[Mobile Development]]></category><dc:creator><![CDATA[Knoxius Maximus]]></dc:creator><pubDate>Thu, 15 Oct 2026 12:43:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ab62fc23d8658bcbd75a8ea/4d13f754-8dd1-4a0d-a7c2-a91b45f823a6.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Field work destroys assumptions that feel fine in an office SPA: flaky LTE, dead basements, clients who want a number <em>now</em>, and photos that must stay attached to the estimate even if sync fails later.</p>
<p>This post walks through an architecture we used for a local-first quoting client aimed at driveway / bay workflows — specifically auto detailing estimates — where offline is the default, not a fallback banner.</p>
<p>The problem (product → engineering)</p>
<p>A quoting tool that requires a round-trip to create a draft is a liability on site. Requirements that drove the stack:</p>
<ol>
<li><p>Create and edit line-item quotes with zero network.</p>
</li>
<li><p>Attach compressed vehicle photos to the same record.</p>
</li>
<li><p>Generate a shareable PDF on device.</p>
</li>
<li><p>Schedule local reminders (draft unfinished, quote expiring, appointment coming up).</p>
</li>
<li><p>Optionally sync premium / ads later — never block the core path on auth.</p>
</li>
</ol>
<p>Those constraints rule out “thin client + always-on API” as the primary path.</p>
<p>Stack choices (and why)</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Choice</th>
<th>Reason</th>
</tr>
</thead>
<tbody><tr>
<td>Shell</td>
<td>Capacitor (Android-first)</td>
<td>Camera, filesystem, share, local notifications with one Web codebase</td>
</tr>
<tr>
<td>UI</td>
<td>React + TypeScript</td>
<td>Fast iteration; shared types for quotes/line items</td>
</tr>
<tr>
<td>Persistence</td>
<td>Dexie (IndexedDB)</td>
<td>Structured local queries, live queries for lists, no server required</td>
</tr>
<tr>
<td>PDF</td>
<td><code>@react-pdf/renderer</code> (lazy)</td>
<td>On-device branded PDFs without a render service</td>
</tr>
<tr>
<td>Monetization</td>
<td>RevenueCat + AdMob (optional gates)</td>
<td>Keep free tier usable; premium removes friction on export</td>
</tr>
</tbody></table>
<p>The important part is not the brand names — it is the local write path: every meaningful user action persists on device first.</p>
<p>Domain model that stays offline-friendly</p>
<p>Keep the quote as an aggregate you can serialize without joins to a remote user graph:</p>
<ul>
<li><p>Quote header: client, address, status (<code>draft</code> / <code>sent</code> / <code>accepted</code> / <code>rejected</code>), totals, validity date</p>
</li>
<li><p>Line items: label, quantity, unit (hour / vehicle / job / stage), rate, amount</p>
</li>
<li><p>Photos: local IDs + compressed data URLs (or filesystem URIs on native)</p>
</li>
<li><p>Derived tools: vehicle size, paint-correction hours, interior multipliers, job time → become line items when the user taps “add to quote”</p>
</li>
</ul>
<p>Statuses matter. A pipeline (“needs action”: unsent drafts, expiring sent quotes, this week’s appointments) is just local queries over timestamps — no CRM server required.</p>
<p>IndexedDB patterns that paid off</p>
<ol>
<li><p>One database, versioned stores<br />Quotes, appointments, and settings in one Dexie DB. Settings hold shop branding (name, logo, rates, PDF theme) so PDF generation stays a pure function of local state.</p>
</li>
<li><p>Live queries for lists<br />History and dashboard screens subscribe to ordered collections. Users see updates immediately after save without inventing a sync event bus.</p>
</li>
<li><p>Draft session separate from committed quotes<br />Autosave a draft snapshot while building. On cold start, offer resume/discard. This reduces “I lost the estimate when the OS killed the WebView” support pain.</p>
</li>
<li><p>Export quota as local counters (free tier)<br />If you gate PDF exports, track unique quote IDs per calendar month in local storage/DB — still works offline; reconcile with entitlements when the store is reachable.</p>
</li>
</ol>
<p>Photos without melting the WebView</p>
<p>Camera / gallery picks should be compressed before persistence. Practical rules we settled on:</p>
<ul>
<li><p>Cap count per quote</p>
</li>
<li><p>Downscale + JPEG/WebP-style compression to data URL or native file</p>
</li>
<li><p>Never block the UI thread on multi-megapixel originals</p>
</li>
<li><p>Keep photos bound to the quote ID so PDF and history stay consistent</p>
</li>
</ul>
<p>Capacitor’s camera plugin + a small compression helper is enough for field reference shots; this is not a Lightroom clone.</p>
<p>On-device PDF as a feature, not a microservice</p>
<p>Generating PDFs locally means:</p>
<ul>
<li><p>Lazy-load the PDF renderer so cold start stays light</p>
</li>
<li><p>Themes (e.g. “canopy” band vs simple invoice) as style variants over the same data</p>
</li>
<li><p>Share via Capacitor Share / Filesystem instead of uploading a blob to your API</p>
</li>
<li><p>Premium variants can omit watermarks or hide internal shop rates on the customer-facing document</p>
</li>
</ul>
<p>Failure mode to design for: renderer exceptions must surface a toast and leave the quote intact.</p>
<p>Reminders without a push backend</p>
<p>Local notifications cover a surprising amount of retention for solo operators:</p>
<ul>
<li><p>Unfinished draft after N hours</p>
</li>
<li><p>Sent quote approaching <code>validUntil</code></p>
</li>
<li><p>Appointment N hours before start</p>
</li>
<li><p>Quiet hours respected in scheduling logic</p>
</li>
</ul>
<p>Rebuild the notification schedule when quotes/appointments change. Deep links back into <code>/quote</code>, <code>/calendar</code>, or <code>/history/:id</code> close the loop.</p>
<p>What we deliberately did <em>not</em> build first</p>
<ul>
<li><p>Multi-user shop permissions</p>
</li>
<li><p>Cloud sync as a launch blocker</p>
</li>
<li><p>Payment capture inside the estimate</p>
</li>
<li><p>Marketplace lead gen</p>
</li>
</ul>
<p>Those can arrive later. The first mile is: accurate number → professional PDF → booked time, with dead radios.</p>
<p>Takeaways</p>
<ol>
<li><p>Model the quote as a complete offline aggregate.</p>
</li>
<li><p>Persist first; treat network as enhancement (entitlements, ads, calendar export URLs).</p>
</li>
<li><p>Use the OS for camera, share, and notifications instead of reinventing them in JS.</p>
</li>
<li><p>Keep PDF generation a pure transform of local state.</p>
</li>
<li><p>Pipeline UX (“needs action”) is mostly date math on local records.</p>
</li>
</ol>
<p>If you are researching local-first contractor tooling, the public overview for the detailing-oriented build lives here: <a href="https://knoxstudios.net/apps/detailbid.html">https://knoxstudios.net/apps/detailbid.html</a></p>
]]></content:encoded></item></channel></rss>